Bug description

To fully simplify my situation but also clearly show the key points, I use some dummy code to describe this bug.

Considering that we want to let our code execute in following logic:

  • If FOO is defined, execute in path A
  • Else, execute in path B

But to our surprise, when ‘FOO’ is ‘not defined’, program still executes in path A.

Original code

FOO is defined:

#include <stdio.h>
#include <stdlib.h>

#define FOO	true

int main(int argc, char **argv)
{
#if FOO == true
	printf("I'm foo.\n");
#else
	printf("I'm not foo.\n");
#endif

	return 0;
}

Compile and Run:

$ gcc misuse_of_if_macro.c ; ./a.out
I'm foo.
#=> this is what we expect

FOO is not defined:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
#if FOO == true
	printf("I'm foo.\n");
#else
	printf("I'm not foo.\n");
#endif

	return 0;
}

Compile and Run:

$ gcc misuse_of_if_macro.c ; ./a.out
I'm foo.
#=> this is not what we expect

Replace ‘true’ with ‘1’

FOO is defined

#include <stdio.h>
#include <stdlib.h>

#define FOO	1

int main(int argc, char **argv)
{
#if FOO == 1
	printf("I'm foo.\n");
#else
	printf("I'm not foo.\n");
#endif

	return 0;
}
$ gcc misuse_of_if_macro.c ; ./a.out
I'm foo.
#=> this is what we expect

FOO is not defined

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
#if FOO == 1
	printf("I'm foo.\n");
#else
	printf("I'm not foo.\n");
#endif

	return 0;
}
$ gcc misuse_of_if_macro.c ; ./a.out
I'm not foo.
#=> this is what we expect

Cause of bug

According to GCC document which describes #if macro:

  • The ‘#if’ directive allows you to test the value of an arithmetic expression, rather than the mere existence of one macro. Its syntax is:
#if expression

controlled text

#endif /* expression */
  • expression is a C expression of integer type, subject to stringent restrictions. It may contain:
    • Integer constants.
    • Character constants.
    • Arithmetic operators.
    • Macros.
    • Uses of the defined operator.
    • Identifiers that are not macros, which are all considered to be the number ZERO.

In our situation, when ‘FOO’ is not defined, ‘FOO’ in ‘#if FOO == true’ is treated as 0. On the other hand, based on the last rule that the GCC document describes, ‘true’ is also treated by GCC preprocessor as 0. Therefore, when ‘FOO’ is not defined, the program goes to the wrong branch.

When we replace ‘true’ with ‘1’, #if is now evaluating a arithmetic expression. So we can get what we want.

In summary, #if macro expects an arithmetic expression, in which a string like ‘true’ is not allowed.

Solution

In this case, we just need to use #ifdef instead of #if since the value inside ‘FOO’ is not concerned at all.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
#ifdef FOO
	printf("I'm foo.\n");
#else
	printf("I'm not foo.\n");
#endif

	return 0;
}

References

[1] https://gcc.gnu.org/onlinedocs/cpp/If.html