【问题标题】:Nulls in variadic C functions可变 C 函数中的空值
【发布时间】:2018-04-13 10:00:43
【问题描述】:

如果我定义了一个可变参数函数:

#include <stdio.h>
#include <stdarg.h>

int f(char*s,...)
{ 
    va_list ap;
    int i=0;
    va_start(ap, s);
    while(s)
    {
       printf("%s ", s);
       i++;
       s=va_arg(ap,char*);
    }
    va_end(ap);
    return i;
}

int main()
{ 
    return f("a","b",0);
}

gcc (linux x64) 编译它,exe 运行并打印“a b”。

是否需要这样的演员表:

return f("a","b",(char*)0)

在通用系统上?

【问题讨论】:

  • 考虑到 sizeof(0) 可能,在 64 位系统上,它小于 sizeof((char*)0)
  • 是的,因为这是一个 varargs 函数,所以需要强制转换:调用者需要知道要传递(“push”)到函数的参数的类型/大小。 (例如,参见 execl()execlp()
  • @joop 是在 linux x64 上“推送”的可变参数?
  • 我引用了“push”这个词,因为C不需要堆栈。 x64 可能会使用具有 32 位或 64 位对齐要求的堆栈。 (但这都是依赖于实现的)
  • 无论你做什么,可变参数函数都是非常不安全的,所以在使用它们时没有好的做法。好的做法是首先不要使用可变参数函数。它们的存在是不良程序设计的某种迹象。

标签: c variadic-functions


【解决方案1】:

编译器不能自动提升可变参数的指针,例如为什么当你想在printf中打印一个指针时你必须将它转换为void *

printf("%p", (void *)ptr);

相同的规则适用于所有可变参数函数,编译器无法知道您的函数需要 char *,而 0 默认情况下只是一个整数,所以是的,您需要将其转换为 (char *)0


即使NULL 也必须使用您的函数(char *)NULL 进行强制转换


那么为什么 main() 会起作用,什么时候会失败?

您的代码并没有真正“工作”。您的代码调用未定义的行为,任何事情都可能发生,所以没有人可以回答这个问题。在这里你很幸运,但下一次可能不会。

【讨论】:

  • @effbiae 这是“未定义的行为”(谷歌)。这包括“显然工作正常”。
  • @effbiae 未定义行为未定义。取决于实现是否可能一直工作,它可能在大多数时间工作,它可能永远工作,它可能一直工作,直到你用另一个编译器编译它或直到你升级你的操作系统等。
  • @effbiae 这是未定义的行为。不要依赖未定义的行为。时期。不存在“较少未定义的行为”之类的东西,它是 UB 还是不是。
  • @effbiae 您不应该依赖代码的实现特定细节,只有标准,特别是在 C 中。linux 可以随时更改,无需温暖您,因为您不应该依赖这种行为,它不是用户空间。 NULL 必须强制转换,因为它是 void * 并且您的函数期望 char *,就像我说的没有自动提升,并且标准对于 char *void * 之间的兼容性有点模糊,怀疑总是强制转换具有可变参数功能的变量。
  • @Lundin “在这种特殊情况下”,女巫之一?这个 ? return f("a","b",0); ?在这种情况下是完全错误的。 “它可以隐式转换为任何对象指针就好了”,你是什么意思?在这种情况下 0 会自动转换为 char * 吗?这也是完全错误的。 “同样,NULL 也可以不用任何转换,因为在 va_arg 内部已经有一种转换方式。” => 您回答自己“但是,如果空指针常量的大小与指针不同,则会出现问题。在这种情况下,需要进行转换。”所以没有 NULL 是不行的。
【解决方案2】:

您的代码应该可以在任何支持SYSV x64 ABI standard 的系统上正常工作。它可能在使用具有类似要求的其他 ABI 标准的系统上运行,并且可能在使用其他 ABI1 的任何系统上失败。根据 C 标准,这都是未定义的。


1特别是,微软不遵循标准的 x64 ABI,他们有自己的标准。

【讨论】:

  • 因为值被提升为 64 位 - 还是 8 字节对齐?
  • @effbiae: both -- x64 ABI 标准中的所有值都是 8 字节/64 位的倍数,并按原样传递。
  • x64 ABI 标准是否定义 NULL 为零?如果不是你的答案是错误的。我的问题是“在公共系统上是否需要像:return f("a","b",(char*)0) 这样的演员?”,对我来说,公共系统意味着“一般”,我很惊讶 OP 有效你的答案,就像如果 OP 只想要以任何价格有效,代码没有被破坏。这就是为什么 Michael Walz 和我不想回答“为什么 main() 有效”,我们不应该鼓励使用未定义行为的人。
  • @Stargateur 没有 x64 ABI 和使用它的程序证明您正在谈论的未定义行为已在 x64 上定义? “人”可能会接触到这样的危险信息并做出自己的选择。问题不在于 C 标准必须说什么。
  • @Stargateur:是的,引用 ABI 标准“空指针(对于所有类型)的值为零”。正如我在对您的回答的评论中所说,有多种已定义/未定义,一个标准中未定义的内容可能在另一个标准中定义。代码只能根据标准可移植——如果它遵循标准,它将可移植到遵循该标准的系统,但不能移植到不遵循该标准的系统。了解您使用的系统哪些标准很重要。
猜你喜欢
  • 2011-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多