考虑以下代码:
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
void foo(const char *arg, ...) {
va_list args_list;
va_start(args_list, arg);
for (const char *str = arg;
str != NULL;
str = va_arg(args_list, const char *)) {
printf("%s\n", str);
}
va_end(args_list);
}
int main(int argc, char **argv) {
foo("Some", "arguments", "for", "foo", NULL);
foo("Some", "arguments", "for", "foo", 0);
return 0;
}
正如我们所看到的,
foo()
使用可变参数列表获取字符串列表并将它们全部打印出来。假设最后一个参数是空指针,因此直到检测到 NULL
时才处理参数列表。
main()
以两种不同的方式调用函数 foo()
,分别使用 NULL
和 0
作为最后一个参数。我的问题是:第二次调用使用
0
作为最后一个参数是否正确?我认为,我们不应该使用
0
调用 foo()
。原因是,在这种情况下,例如,编译器无法从上下文中猜测出 0
应该被视为空指针。因此,它将其处理为普通整数。然后,foo()
处理 0
并将其转换为 const char*
。当空指针具有与 0
不同的内部表示时,就会出现魔法。据我所知,这会导致检查 str != NULL
失败(因为在我们的情况下,str
将等于转换为 const char*
的 0
,它与空指针不同),从而导致程序行为错误。我的想法正确吗?任何好的解释都会受到赞赏。
#define NULL ((void *)0)
,因此这是非常安全的。另外,将其转换为void *
是足够的。 - too honest for this siteNULL
定义为整数常量0转换为void*
,因此在所有类POSIX系统上第一个调用是正确的。 - fuzint
和64位指针在堆栈上传递参数的平台,其中空指针是所有位都为0。当您只传递0时,编译器将32位0推送到堆栈上。但是当您使用const char *
调用va_arg
时,编译器从该地址获取64位。其他32位可能是任何旧垃圾,它们恰好占据了接下来的几个字节... - Nate Eldredgefoo()
将得到一个非空指针,并将其视为非哨兵。它将尝试将其视为字符串指针。但是由于它包含垃圾数据,程序可能会崩溃(或执行其他不良操作)。我依稀记得曾经追踪过一个错误,其中某人使用最后一个参数0调用了execlp()
,这正是发生的事情。 - Nate Eldredgeconst char *
。在这种情况下,整数0从未被强制转换为指针(这将意味着编译器有机会将0转换为适当的空指针)。您只是从存储int的位置获取指针,而这样做可能会出现许多问题。 - Nate Eldredge