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
"The difference with targets is that you can do:
hw_init();
and the hw_init() from the specific target_xx.c will be called. So you get very clean code with few #ifdef section"
Like I said before it is EXACTLY how my code works. The only difference is that you add your target_xx.c file directly to your project and I include it in preprocessor:
random_source_file.c:
#if defined(HARDWARE_1) #include "config_hw_1.c" #elif defined(HARDWARE_2) #include "config_hw_2.c" #else #error "Undefined hardware." #endif
random_source_file.h:
#if defined(HARDWARE_1) #include "config_hw_1.h" #elif defined(HARDWARE_2) #include "config_hw_2.h" #else #error "Undefined hardware." #endif
I don't see an overabundance of #if statements here and the code is pretty straightforward.
As far as a bug goes - I never said that I included code in the header files! Where are you getting it from? In the example above let's say HARDWARE_1 is defined. I compile code and put a valid breakepoint in the code in config_hw_1.c. Don't see anything "quite far from normal best practices" so far. I run the the program and it does break however not in the place of the breakpoint and not even at the same file. It places the yellow marker someplace else and not even on the code line but at some comments.
I thought about it twice and I'm calling it a bug.
I've thought about what you've written and responded with more than twice now. Better I don't say what conclusion I've come up with!
"As far as a bug goes - I never said that I included code in the header files! "
The big issue here is the following sequence, where you pretend that "hardware_module_1.c" is a header file and decides to #include it.
#ifdef HARDWARE_MODULE_1 #include "hardware_module_1.c" #endif
It doesn't matter if you include a *.c file from a *.h file or from a *.c file. The thing is that you should _not_ include a *.c file. If you want breakpoints, you should keep the code in the c file, and have the compiler directly process the c file.
The reason the C language got the concept of header files, is that the *.c (header files) are intended to be #include'd. 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.
You made the assumption that you should get a good result by including function implementations. Your assumption was wrong. And to my knowledge, Keil hasn't promised that they will have explicit support for breakpoints in code that has been #include'd.
"I don't see an overabundance of #if statements here and the code is pretty straightforward."
What you fail to notice here, is that while there exists two different alternatives that gives the same number of #ifdef needed, one alternative only includes function prototypes and one includes the actual implementation. So one alternative does what #include is intended to be used for, while the other alternative does not. One alternative works, and one alternative breaks your debugging.
So once more - #include is intended for header files. *.c are not header files. Whenever you decides to #include a *.c file - or place the actual implementation of functions in a *.h file, you'll have to accept the problems that follows. It is possible to drive a car to/from work in the reverse gear. But you can't call it a bug if the car is hard to control, gives you a pained neck, or if the police stops you because the break lights are on the wrong side of the vehicle in relation to your travel direction. The way you use #include is contrary to best practices - and you get to suffer the problems that follows from selecting a bad solution.
It's up to you to decide if you want to continue to complain about Keil, or if you decide to change your design and code as the language creators intended the language to be used. But no one will care about your complaints as long as you continue to do your transports with the car in reverse gear.
"I thought about it twice and I'm calling it a bug."
We can agree on that. Just that it isn't a bug in the Keil tools, but in code far closer to you.
"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.