9

编译以下代码:

#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);
   ^

但是,如果是因为检查,strdup则不会被调用。有什么办法可以避免这个警告?FOONULLif (FOO)

谢谢!

4

3 回答 3

6

你是正确的,你已经保护strdup了一个子句的调用,以确保strdup永远不会用NULL参数调用它。

但是为函数调用发出警告的编译器部分与知道调用永远不会发生的部分不同。

相反,您可能会用一个表达式来掩盖 ,NULL以确保生成的参数表达式永远不会是NULL

例如

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

或者

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

strdup这是不能用值调用的“明确”(至少对编译器而言)NULL,并且您的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是一些正式的符号和确认。

于 2017-06-23T11:11:29.687 回答
4

如果想法是为fooifFOO定义分配一个值,您可以尝试:

//#define FOO "lorem ipsum"

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

它还有一个优点,即if不需要时不包含整个代码。

于 2014-10-22T13:06:19.953 回答
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)

显然是假的,因此,我个人确实认为这是编译器的一个缺点,因为编译器证明 x 在传递给 strdup 时不能为 null 应该是微不足道的,即使我们明确传递 NULL (应该优化整个分支,因为获取代码的条件明确为假)。

这(保持宏语义)效果更好:

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()。是的,这是一个杂乱无章的东西,而且很讨厌,但它确实有效。

于 2021-03-22T08:14:25.853 回答