如何避免“null argument where non-null required”的编译器警告。

12

编译以下代码:

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

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

    if (FOO)
        foo = strdup(FOO);

    return 0;
}

导致以下编译器警告:

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

然而,如果FOONULL,由于if (FOO)检查,strdup将不会被调用。有没有什么方法可以避免这个警告?

谢谢!


1
预处理后,代码将变为:if ((NULL)) foo = strdup((NULL));,这很可能是错误的原因。strdup 需要一个不是 NULLconst char * - bzeaman
3
调用strdup(NULL)是无意义的,所以不必费心。警告是正确的。 - Jabberwocky
你正在进行未定义的行为,但是-Wno-nonnull将禁用警告。[GCC手册。](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) - user3920237
1
如果您转到strdup的声明,您会发现在您的实现中它是使用__attribute__((nonnull (1)))属性(或者__attribute__((nonnull)))声明的。这并非偶然,而且如果不禁用非空属性检查,结果就是您现在得到的结果。 - WhozCraig
你如何编译你的代码?那个警告可能会因为优化而改变。尝试使用 gcc -Wall -Wextra -O - Basile Starynkevitch
1
@dasblinkenlight,我认为这不是编译器的错误。如果NULL扩展为(void*)0,预处理器会将其解释为数字。void被替换为0,所以#if看到的是(0*)0,这是一个语法错误。NULL在预处理指令中不适用。 - Jens Gustedt
3个回答

8
您说得对,您已经使用了一个从句来保护对strdup的调用,以确保strdup从不使用NULL参数进行调用。
但是发出函数调用警告的编译器部分与知道调用永远不会发生的部分不同。
相反,您可以使用表达式来遮蔽NULL,以确保生成的参数表达式永远不会为NULL
例如:
if (FOO) foo = strdup(FOO?FOO:"");

或者

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

以下是“清晰的”(至少对编译器而言)信息:不能使用NULL值调用strdup,并且您的if子句确保它不会再次调用已经不是NULL值的内容。

此时我们挥舞手中的魔杖,并说编译器将把所有内容进行优化,为了帮助我们视觉化优化,我们有:

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

对于调试版本,可以使用如下内容:

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

我们可以利用GNU扩展?:(其中可选的缺失中间子句默认为第一个子句)来避免多次评估(x)
#define NON_NULL(x) ((x)?:"")

对于调试版本,类似以下内容:

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

现在,您可以展示一些技术上更加晦涩但显然更有意义的内容:
if (FOO) foo = strdup(NON_NULL(FOO));

假装 NON_NULL 是某种正式表示和确认。


4
如果想要在FOO已定义的情况下给foo赋值,可以尝试以下方法:
//#define FOO "lorem ipsum"

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

它还有一个优点,就是当不需要时,整个if代码不会被包含在内。

1
@Deduplicator 你使用哪个编译器?如果 FOO 是一个字符串,VC++ 会给出 "invalid integer constant expression",GCC 则会给出 "token ... is not valid in preprocessor expressions"。尽管乍一看,根据标准,#if 要求的是一个常量表达式,而不是一个 整数 常量表达式。 - AlexD

0
另一种策略是使用静态内联函数,我也遇到了同样的问题。所以(在我看来)这确实是一个编译问题,让我们考虑一下:
#define foo(v, x)  do { v = x ? strdup(x) : NULL; } while(0)

使用 -W -Wall -Werror 这个选项

char *bar;
foo(bar, NULL);

将会失败

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)

这显然是伪造的,因此我个人确实相信这是编译器的缺陷,因为编译器应该可以轻松地证明在传递给strdup时x不可能为空,即使我们明确传递了NULL(整个分支应该被优化掉,因为到达代码的条件明确是false)。
保持宏语义会更好:
static inline 
void foo(char* &v, char* x) {
    v = x ? strdup(x) : NULL;
}

但是“改进”的语义可能也可以是:
static inline 
char* foo(char* x) {
    return x ? strdup(x) : NULL;
}

bar = foo(NULL);

我敢打赌这不会解决所有情况,但是可以将其用作strdup_if_not_null(),并在宏中使用它,而不是使用strdup()。 是的,这很拙劣,也很讨厌,但它确实有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接