I would like to do conditional compiling and include different C files based on the hardware being used. For example:
#ifdef HARDWARE_MODULE_1
#include "hardware_module_1.c"
#endif
#ifdef HARDWARE_MODULE_2
#include "hardware_module_2.c"
...
Everything works fine this way only debugging becomes a problem. Breakpoints don't work correctly when placed in the C file that's included that way. The program ends up breaking somewhere else random in the code (different C file altogether) When I manually add file to the project everything seems to be working fine. I'm using uVision V4.60.6.10
"And to my knowledge, Keil hasn't promised that they will have explicit support for breakpoints in code that has been #include'd"
Neither does it make the opposite statement. Besides, Including C file into another C file is legal from the language standpoint.
" And the *.c files are intended to be sent to the compiler to produce object files that can then be linked. So each object file is the result of one (1) *.c file"
There is nothing wrong with producing a single compilation unit from several source files.
"You made the assumption that you should get a good result by including function implementations. Your assumption was wrong."
Why? For all your pontification and colorful albeit misapplied metaphors you failed to give a factual reason for what exactly goes wrong when you include .c file.
There is also another advantage in my approach. Other tools (or IDE's) will compile the project correctly.
One more thing. C language (and compiler) doesn't have a concept of source files and header files. It is just merely a convention. So preprocessor just copies and pastes them before compilation begins.
"Neither does it make the opposite statement."
Irrelevant. A bug is when the software functionality breaches the contract. What isn't part of the contract can't be a bug - but is quite likely a bad assumption.
If the car manufacturer promises the car can do 200 km/h, then it must be able to do that. But it doesn't tell if the car can do 220 km/h or not.
"Besides, Including C file into another C file is legal from the language standpoint."
The world is full of constructs that are legal from the language standpoint. The language allows you to write code that does division by zero - but the outcome of running the code will most probably not be a big success.
In your specific case, the compiler do produce a working program. It's just that you assume that you should also be able to have breakpoints in the included files. And a breakpoint in "hardware_1.c" would normally have the debug information in "hardware_1.o", but you never produced any "hardware_1.o"...
"There is nothing wrong with producing a single compilation unit from several source files."
That depends on your assumptions of the outcome, outside of the contract of the compiler being able to produce working programs.
The preprocessor will merge all #include into a single file that the compiler will process. But the line numbers in this merged file can't be used while debugging, since the debugger will not be able to open such a merged file.
So the preprocessor might then decide to not just insert line numbers, but also file names in the merged data to allow the debugger to not just map to different lines in the source file, but to map to lines in the different included files. But the thing here is that you need to separate what is required by the language, and what is implementation decisions when implementing the compiler, the debug format and the debugger.
"For all your pontification and colorful albeit misapplied metaphors you failed to give a factual reason for what exactly goes wrong when you include .c file."
Not actually true - you just decided to brush aside what I wrote.
Just as you can get a car with 2 doors or 5 doors, different compilers will have different functionality - there are a lot of things that aren't locked down by the language standard. Compilers that also supports C++ are much more likely to support breakpoints in header files since it's part of the C++ concept to have implementation code in header files.
Here is, by the way, a link to an older version of gcc that couldn't handle breakpoints in header files because the debug information could not handle more than one source file name per object file: www.delorie.com/.../faq12_6.html
That just about "all" C++ compilers supports breakpoints in header files doesn't mean you can expect all C compilers to do it. Since a C++ compiler needs to handle code in header files, a C++ compiler would use a debug format that supports it. And would use the same debug format when compiling C code.
The problem here is that most compilers you ever see something written about on the net are C/C++ compilers, i.e. capable of supporting both languages. So people makes assumptions based on only having seen luxury implementations. For many years, a huge number of people constantly reported bugs all over the net whenever a C compiler couldn't handle // end-of-line comments. Just because they assumed it was part of the C language standard since a very large number of compilers did add that functionality as a non-standard extension when compiling C.
"There is also another advantage in my approach. Other tools (or IDE's) will compile the project correctly."
The same thing would be true if each individual C file is separately compiled but with all code inside an #if/#endif block, and either assumes that you either have a global define in the project file/Makefile or a single header file that specifies the defined symbol. And then you would get debugging to work with all compilers that supports the generation of debug information.
"So preprocessor just copies and pastes them before compilation begins."
But that is a huge simplification that isn't really fully applicable when you start to thin about debugging.
What if I do this?
#define PREFIX "com1_" #define RXSIZE 32 #define TXSIZE 32 #include "uart.c" #undef PREFIX #undef RXSIZE #undef TXSIZE #define PREFIX "com2_" #define RXSIZE 128 #define TXSIZE 1024 #include "uart.c" ... com1_init(9600); com2_init(115200);
and then in "uart.c" have
uint8_t _(rxbuf)[RXSIZE]; uint8_t _(txbuf)[TXSIZE]; void _(init)(void) { ... }
The above can be correct C code. And the compiler can manage to produce a valid program. But every line in "uart.c" is used to produce two separate serial port implementations. So creative use of the preprocessor resulted in "template" support in C.
But a breakpoint at uart.c:152 would be the same source line that is used in both com1_init() and com2_init(). How would the IDE and the debugger then know how to translate from a source line into an offset into the binary when you try to set a breakpoint? Or should the IDE+debugger recognize the above and set a breakpoint both in com1_init() and in com2_init()? But if you then wait until the debugger breaks on com1_init() and you then press a key to remove a breakpoint - should both breakpoints be removed? Or just the com1_init() breakpoint?
C++ compiler vendors have to invest a huge amount of time to try to see how far they can get their debuggers to handle included code, because of class method code, template code, inlined code, ...
So "valid code" does not mean you have the right to demand that the debugger - any debugger - will be able to reverse-engineer what to actually do in all situations.
In the end, each and every developer will still have to stand for the consequences if they make an incorrect assumption.