12

Compiling the following code:

#include <string.h>
#define FOO (NULL)

int main(int argc, char *argv[])
{
    char *foo;

    if (FOO)
        foo = strdup(FOO);

    return 0;
}

results in the following compiler warning:

foo.c: In function ‘main’:
foo.c:9:3: warning: null argument where non-null required (argument 1) [-Wnonnull]
   foo = strdup(FOO);
   ^

However, strdup won't be called if FOO is NULL because of the if (FOO) check. Is there any way to avoid this warning?

Thanks!

profzoom
  • 297
  • 1
  • 2
  • 10
  • 1
    After preprocessing this will read: `if ((NULL)) foo = strdup((NULL));`, which is probably the cause of the error. `strdup` wants a `const char *` that's not `NULL`. – bzeaman Oct 22 '14 at 13:02
  • 3
    Calling `strdup(NULL)` is nonsense anyway, so don't bother. The warning is correct. – Jabberwocky Oct 22 '14 at 13:02
  • What you're doing is undefined behavior, but `-Wno-nonnull` will disable the warning. [GCC manual.](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) –  Oct 22 '14 at 13:04
  • 1
    if you goto the decl for `strdup`, you will find on your implementation it is declared with the [`__attribute__((nonnull (1)))`](https://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Function-Attributes.html) attribute (or `__attribute__((nonnull))`). That isn't by accident, and without *disabling* the non-null attribute check, the result is what you get now. – WhozCraig Oct 22 '14 at 13:06
  • How do you compile your code? That warning might change with optimizations. Try `gcc -Wall -Wextra -O` ! – Basile Starynkevitch Oct 22 '14 at 13:12
  • 1
    @dasblinkenlight, I don't think that this is a compiler bug. If `NULL` expands to `(void*)0` the preprocessor wants to interpret this as a number. `void` is replaced by `0`, so the `#if` sees `(0*)0` which is a syntax error. `NULL` is not suitable in preprocessor directives. – Jens Gustedt Oct 22 '14 at 15:33

3 Answers3

8

You are correct that you have protected invocation of strdup with a clause to ensure that strdup is never called with a NULL argument.

But the part of the compiler that emits the warning for the function invocation isn't the same part that knows that the invocation can never happen.

You might instead obscure the NULL with an expression that ensure that the generated argument expression can never be NULL.

e.g.

if (FOO) foo = strdup(FOO?FOO:"");

or

if (FOO) foo = strdup(FOO + !FOO);

Here is is "clear" (to the compiler at least) that strdup cannot be called with a NULL value, and your if clause makes sure that it is never called with what was is no longer a NULL value.

At this point we wave our hands and say that the compiler will optimise it all away, and to help us visually optimise it all away, we have:

#define NON_NULL(x) ((x)?(x):"")

and for debug builds, something like:

#define NON_NULL(x) ((x)?(x):(abort(),""))

We might makes use of the GNU extension ?: (with optional missing middle clause defaulting to the first clause) to avoid evaluating (x) more than once.

#define NON_NULL(x) ((x)?:"")

and for debug builds, something like:

#define NON_NULL(x) ((x)?:(abort(),"")

Now you can present something that is technically more obscure but apparently more meaningful:

if (FOO) foo = strdup(NON_NULL(FOO));

And pretend that NON_NULL is some formal notation and acknowledgement.

Sam Liddicott
  • 1,265
  • 12
  • 24
4

If the idea is to assign a value to foo if FOO is defined, you could try:

//#define FOO "lorem ipsum"

int main()
{
    char *foo;
    #ifdef FOO
        foo = strdup(FOO);
    #endif
}

It also has an advantage that the entire if code is not included when not needed.

AlexD
  • 32,156
  • 3
  • 71
  • 65
  • 1
    @Deduplicator Which compiler do you use? If `FOO` is a string, VC++ gives "_invalid integer constant expression_", GCC - "_token ... is not valid in preprocessor expressions_". (Although at the first glance, according to the standard, `#if` requires a constant expression, not an _integer_ constant expression.) – AlexD Oct 22 '14 at 20:35
0

Another strategy is to use static inline functions, I just bumped into the same thing. So this (in my opinion) really is a compile issue, let's consider this:

#define foo(v, x)  do { v = x ? strdup(x) : NULL; } while(0)

Using -W -Wall -Werror this

char *bar;
foo(bar, NULL);

will fail with

nonnull.C:3:40: error: null argument where non-null required (argument 1) [-Werror=nonnull]
    3 | #define foo(v, x) do { v = x ? strdup(x) : NULL; } while(0)

This is obviously bogus, and as such, I personally do believe this is a compiler shortcoming since it should be trivial for the compiler to prove that x cannot be null at the time of passing to strdup, even if we're explicitly passing NULL (the entire branch should be optimized out since the condition to get to the code is explicitly false).

This (keeping the macro semantics) works better:

static inline 
void foo(char* &v, char* x) {
    v = x ? strdup(x) : NULL;
}

But "improved" semantics may just as well be:

static inline 
char* foo(char* x) {
    return x ? strdup(x) : NULL;
}

bar = foo(NULL);

I'm betting this won't solve all cases, but one could use above as strdup_if_not_null() and use that inside your macros instead of strdup(). Yes, it's a kludge, and it's nasty, but it does work.

Jaco Kroon
  • 69
  • 1
  • 7