2

Header file config.h contains the following statements:

#define RED 0
#define BLUE 1
#define GREEN 2
#define CONFIG_COLOR RED

Source file main.c does not include config.h but contains the following:

#if CONFIG_COLOR == RED
/* Red code */
#elif CONFIG_COLOR == BLUE
/* Blue code */
#elif CONFIG_COLOR == GREEN
/* Green code */
#endif

The ARM compiler ARMCC (or rather, the C pre-processor) does not issue any warning or error when compiling main.c although CONFIG_COLOR is not defined.

Is there a way to force the compiler to issue an error in the above case?

Alternatively, is there a way to force Keil ARM uVision IDE to always include config.h for every source file?

I know that in GCC there are flags to do both.

s7amuser
  • 827
  • 6
  • 18
  • Are you sure `main.c` is not including *something* that is including `config.h`? – Eugene Sh. Feb 06 '17 at 17:27
  • @EugeneSh. 100% Sure. – s7amuser Feb 06 '17 at 17:28
  • You can check if `CONFIG_COLOR` is a predefined macro by using `#if !defined(CONFIG_COLOR)`, `#error CONFIG_COLOR is not defined!`, `#endif`. – Nominal Animal Feb 06 '17 at 17:28
  • Take a look [here](https://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp_4.html#SEC38). Specifically "*Identifiers that are not macros, which are all considered to be the number zero. This allows you to write #if MACRO instead of #ifdef MACRO, if you know that MACRO, when defined, will always have a nonzero value. Function-like macros used without their function call parentheses are also treated as zero.*" So no, you can't "force" it. Only write some extra condition. – Eugene Sh. Feb 06 '17 at 17:29
  • 1
    I'm in full agreement with Eugene Sh. above. So, if `RED`, `GREEN`, and `BLUE` were defined to unique integers larger than zero, then you could use `#if defined(CONFIG_COLOR)`, `#if CONFIG_COLOR-0 == RED-0`, `/* Red case */`, `#elif CONFIG_COLOR-0 == GREEN-0`, `/* Green case */`, `#elif CONFIG_COLOR-0 == BLUE-0`, `/* Blue case */`, `#else`, `#error Invalid CONFIG_COLOR`, `#endif`, `#else`, `#error CONFIG_COLOR is not defined!`, `#endif`. – Nominal Animal Feb 06 '17 at 17:32
  • @NominalAnimal I know this is possible but I prefer not to do that in every file that uses the definitions because it can easily be forgotten. I want the compiler to issue a warning or error in this case instead of relying on the programmer not to forget. – s7amuser Feb 06 '17 at 17:32
  • @s7amuser: That's what the [`#warning` and `#error`](https://gcc.gnu.org/onlinedocs/cpp/Diagnostics.html) directives do. – Nominal Animal Feb 06 '17 at 17:33
  • Then don't use the number `0` for a legal color. Such that you will get some errors while trying to compile with illegal one. – Eugene Sh. Feb 06 '17 at 17:34
  • @EugeneSh. Unfortunately, not using `0` does not help because both `CONFIG_COLOR` and `RED` are not defined and thus by default both equal to 0... – s7amuser Feb 06 '17 at 17:37
  • Then switch from `CONFIG_COLOR` to `COLOR_IS_RED`, `COLOR_IS_GREEN`, and `COLOR_IS_BLUE` macros; i.e. `#if defined(COLOR_IS_RED)`, `/* Red case */`, `#elif defined(COLOR_IS_GREEN)`, `/* Green case */`, `#elif defined(COLOR_IS_BLUE)`, `/* Blue case */`, `#else`, `/* No color selected */`, `#endif`. Ought to be easier for users, too. – Nominal Animal Feb 06 '17 at 17:41
  • @NominalAnimal Switching the way you suggested won't work because the programmer won't know if he forgets to include the header. The #else will be executed without any warning or error. – s7amuser Feb 06 '17 at 17:44
  • @s7amuser He will know, as these macros should be defined in this header. – Eugene Sh. Feb 06 '17 at 17:44
  • @EugeneSh. How will he know if he forgets to include the header? None of the `COLOR_IS_X` will be defined and the `#else` will be compiled without any warning. – s7amuser Feb 06 '17 at 17:46
  • @s7amuser Every `#if COLOR_IS_X` will evaluate to false, so you will get compilation errors as no necessary code will get compiled. There is no `else` in your example.. – Eugene Sh. Feb 06 '17 at 17:49
  • @EugeneSh. In my example indeed no code will be compiled but in case it is not a "necessary code", say a different initialization function in each case, I won't find out until run-time (or worse... after an hour of debugging). – s7amuser Feb 06 '17 at 17:58
  • Did not get why you don't want to use `#ifdef` or `#warning` or `#error` ? – Meninx - メネンックス Feb 06 '17 at 17:59
  • @Meninx-メネンックス Because this part is the non-trusted "programmer" responsibility. – Eugene Sh. Feb 06 '17 at 18:00
  • As a solution use **[Pair Programming](https://en.wikipedia.org/wiki/Pair_programming)** – Meninx - メネンックス Feb 06 '17 at 18:06

2 Answers2

2

Whenever a symbol is not defined in an #if/#elif directive, it is simply replaced by 0. Thus, if CONFIG_COLOR is in no way being defined, your code is the same as:

#if 0 == RED
/* Red code */
#elif 0 == BLUE
/* Blue code */
#elif 0 == GREEN
/* Green code */
#endif

This can lead to hideous bugs if, for instance, RED is defined as 0, and thus the first condition becomes true. You can conditionally halt compilation with an error if CONFIG_COLOR is not defined through #error:

#ifndef CONFIG_COLOR
    #error CONFIG_COLOR is not defined!
#endif

You can also give GCC/Clang the -Wl,-Wundef option to throw a warning whenever this happens. This can be made into a full-blowing error by means of the -Wl,-Werror option.

3442
  • 8,248
  • 2
  • 19
  • 41
  • As I wrote in the comments to the original question, I know this is possible but in this way the programmer *must* not forget to put the #error directive. I prefer the compiler (which obviously has the necessary knowledge) to inform the programmer in this case. In GCC this is possible. – s7amuser Feb 06 '17 at 17:36
  • *How* is it possible in GCC? – Eugene Sh. Feb 06 '17 at 17:36
  • @s7amuser: See my edit. GCC has an option to do just that. – 3442 Feb 06 '17 at 17:37
  • @KemyLand Thank you, I know GCC has this option. – s7amuser Feb 06 '17 at 17:39
  • I see. From googling a bit it appears that `armcc` just doesn't have the analogous option. – Eugene Sh. Feb 06 '17 at 17:42
  • @EugeneSh: Those options are actually "proxies" that carry the particular options (`-Wundef` and `-Werror`) to the C/C++ preprocessor, which is invoked as a separate program, `cpp`, by the compiler driver, whether the later is `gcc` or `armcc`. There should definitively be a way to trick this in your favor, such as manually giving the source to `cpp` before passing the result to `armcc`. – 3442 Feb 06 '17 at 17:45
  • @KemyLand `armcc` is a proprietary implementation, it has it's own preproscessor AFAIK. – Eugene Sh. Feb 06 '17 at 17:46
  • @EugeneSh You should still be able to run GNU `cpp` beforehand manually. Anyways, the "proper" way to do this should be through `#ifndef`/`#error` guards, not through compiler options. – 3442 Feb 06 '17 at 17:47
  • @KemyLand Maybe there is an option to run GNU `cpp` beforehand manually but as I am sure you understand, I hoped for a simpler solution... – s7amuser Feb 06 '17 at 17:50
  • Just trust the programmer :) apparently he will find his own errors... If he won't - I wouldn't call him programmer.. – Eugene Sh. Feb 06 '17 at 17:53
  • @KemyLand I don't agree. I think using the compiler in this case is the safest and most "proper" option. An important job of the compiler is to issue warnings and errors to make the programmer's life easier and allow safer coding and much easier debugging. The programmer has enough responsibility as it is and such tasks can easily be done by the compiler. Anyway, this is already a rather philosophical issue so I don't want to argue about it. – s7amuser Feb 06 '17 at 17:55
  • @s7amuser: Of course that's the compiler's job. However, the preprocessor, which is technically part of the translator but not of the compiler, has a nasty, old design that never got out of the 70s. The preprocessor is *not* your friend. It has lots of non-obvious pitfalls just as this one. The reason that the compiler won't warn you abut this is that replacing the macro's name with a `0` is standard behavior, and that got into the standard because that's what pre-standard preprocessors did. Beware of the preprocessor. It will attempt to make your live a nasty one. – 3442 Feb 06 '17 at 18:00
  • @KemyLand Yeap, unfortunately, this is the case. – s7amuser Feb 06 '17 at 18:03
  • @KemyLand Thank you for the answer. – s7amuser Feb 06 '17 at 18:10
1

In summary, the underlying problem seems to be the fact that the armcc C preprocessor/compiler does not have an option to warn if undefined preprocessor macros are used in #if preprocessor directives, and the downstream developers need hints to get their code to work correctly.


My first instinct is, as I mentioned in a comment, to use preprocessor macros to indicate if a color is selected, rather than which color is selected. In this case, the code block becomes

#if defined(CONFIG_COLOR_IS_RED)
/* Red case */
#elif defined(CONFIG_COLOR_IS_GREEN)
/* Green case */
#elif defined(CONFIG_COLOR_IS_BLUE)
/* Blue case */
#else
#error config.h is not included!
#endif

The downstream developers must include the entire block, of course. (Which seems to be an issue for OP; the downstreamers might be likely to forget or mis-edit the block.)


Another option would be to do the selection at the expression level (as opposed to block level); i.e. using a macro

CONFIG_COLOR(red-expression, green-expression, blue-expression)

which is defined in config.h as, for example,

#if   defined(CONFIG_COLOR_IS_RED)
#define CONFIG_COLOR(red, green, blue) (red)
#elif defined(CONFIG_COLOR_IS_GREEN)
#define CONFIG_COLOR(red, green, blue) (green)
#elif defined(CONFIG_COLOR_IS_BLUE)
#define CONFIG_COLOR(red, green, blue) (blue)
#else
#error Color not configured!
#endif

In this case, if the header file is not included, the compiler should warn at compile time about undeclared symbol CONFIG_COLOR, and at link time refuse to link because symbol CONFIG_COLOR is undefined.

In this case the user code is quite simple, for example

my_color_attribute = CONFIG_COLOR( my_red, my_green, my_blue );

or constants or expressions instead of my_red, my_green, and my_blue. As you see, the macro chooses one value out of three, depending on which "color" is selected.


If block level code is required, then I'd suggest using macros

IF_RED
    /* Red case */
ENDIF
IF_GREEN
    /* Green case */
ENDIF
IF_BLUE
    /* Blue case */
ENDIF

where the macros are defined in config.h as e.g.

#if   defined(COLOR_IS_RED)
#define IF_RED   if (1) {
#define IF_GREEN if (0) {
#define IF_BLUE  if (0) {
#elif defined(COLOR_IS_GREEN)
#define IF_RED   if (0) {
#define IF_GREEN if (1) {
#define IF_BLUE  if (0) {
#elif defined(COLOR_IS_BLUE)
#define IF_RED   if (0) {
#define IF_GREEN if (0) {
#define IF_BLUE  if (1) {
#else
#error Color not selected!
#endif
#define END_IF   }

which generates defunct code (code that will never be reached) for the non-selected code cases; the compiler should be able to optimize the branches away, although I'm not sure if/which options are needed for armcc.

If the config.h header file is not included, then the compiler will choke on the IF_RED etc. symbols as undefined.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • This are delicate hacks. They surely get around the issue, but them throwing seemingly random syntax errors would be fun. – 3442 Feb 06 '17 at 18:02
  • @KemyLand: What do you mean by *"seemingly random syntax errors"*? The cases where `config.h` is not included yields very distinct compiler warning and link-time error messages. If `config.h` is already included, the user can be expected to read the documentation, where it is explicitly mention what kind of error messages can be expected from misuse. I don't see anything *"seemingly random"* here, to be honest. And, if the downstream developers are truly that unreliable, I'd say these are not even *"hacks"* , just padded cluebats. – Nominal Animal Feb 06 '17 at 18:09
  • @NominalAnimal Thank you for the answer. I am still unsure which one is the better solution. Maybe eventually I will put an #error if `CONFIG_COLOR` is not defined... – s7amuser Feb 06 '17 at 18:13
  • @NominalAnimal: So far there are no "seemingly random syntax errors" with the code you're showing. Please forgive me, since I didn't try to say that. Rather, I'm saying that, for instance, chopping the opening brackets from one of the `#define`s would cause hard-to-find syntax errors. – 3442 Feb 06 '17 at 18:13
  • @KemyLand: I don't mind criticism, I just want to know the exact details so I can determine how to fix the issues referenced. I do make a lot of errors, you see. Now, as I understand, those `#define`s would be in the `config.h` file, and ought not to be modified by the downstream developers. In fact, they could even reside in an another file, an internal header file `#include`d by `config.h`, with a big comment at the top saying *"Keep your mittens off this file, internal stuff!"*. – Nominal Animal Feb 06 '17 at 18:18
  • @NominalAnimal: I agree 100%, just noting that this is not to be taking as usual practice. A hack a day keeps the preprocessor away ;-). – 3442 Feb 06 '17 at 18:20
  • @KemyLand: Absolutely agreed. Other than an educational environment -- and there I can see such header files used with e.g. microcontrollers, as they are quite fiddly beasts and even semi-bricked easily (requiring JTAGging to recover) -- I would not do anything like this. I would definitely just write comprehensive documentation, with example use cases, to make their work easier; but I would not try to be there peeking over their shoulder like this all the time. – Nominal Animal Feb 06 '17 at 18:24