【问题标题】:Can the compiler cast `(void *) 0` in `execl(prog, arg, (void*) 0)` to a null pointer of the appropriate type?编译器能否将 `execl(prog, arg, (void*) 0)` 中的 `(void *) 0` 转换为适当类型的空指针?
【发布时间】:2019-02-11 04:45:10
【问题描述】:

来自 Linux 编程接口

execl(prog, arg, (char *) 0);
execl(prog, arg, (char *) NULL);

以上面最后一次调用的方式投射 NULL 是 通常需要,甚至NULL 的实现上 定义为(void *) 0

这是因为,尽管 C 标准要求空指针 不同类型的比较应该测试真以进行相等性比较,它们 不要求不同类型的指针具有相同的内部 表示(尽管在大多数实现中它们都这样做)。
而且,在可变参数函数中,编译器不能转换(void *) 0 指向适当类型的空指针

C 标准对指针的规则做了一个例外 不同类型的不需要有相同的表示:指针 char *void * 类型必须具有相同的 内部代表。这意味着通过(void *) 0 而不是(char *) 0 在示例情况下不会有问题 execl(),但在一般情况下,需要演员表。

  1. “一般需要按照上面最后一次调用的方式投射NULL

    C 标准是否要求空指针与(char*) 0 表示相同?

  2. “在 execl() 等可变参数函数中,编译器无法将 (void *) 0 强制转换为适当类型的空指针。”

    (void *) 0 不是某个类型的空指针吗?

    如果是,为什么编译器不能将execl(prog, arg, (void*) 0) 中的(void *) 0 转换为“适当类型的空指针”?

  3. char *void * 类型的指针必须具有相同的内部表示。这意味着在execl() 的示例情况下传递(void *) 0 而不是(char *) 0 不会有问题” .

    编译器现在可以将execl(prog, arg, (void*) 0) 中的(void *) 0 转换为“适当类型的空指针”吗?

    为什么它与我第 2 点中的引用相矛盾?

  4. 1234563类型”? 谢谢。
  5. 对于非可变函数调用,例如sigaction(SIGINT, &sa, (int*) 0),编译器能否将(int *) 0 转换为“适当类型的空指针”?

谢谢。

【问题讨论】:

  • 请注意,C 不是唯一的相关标准:execl()sigaction() 定义在 POSIX.1-2008 标准中,也称为 IEEE Std 1003.1-2008。考虑添加posix 标签。
  • 我相信 POSIX 需要这个才能工作,但我在移动设备上,手头没有引用。
  • 惊人的问题!!我在阅读时遇到了同样的问题

标签: c pointers posix null-pointer


【解决方案1】:

首先,编译器在任何情况下都不会“强制转换”。强制转换是源代码中请求转换的语法结构。

我假设当您谈论“编译器转换”时,您的意思是谈论 隐式转换,这是一种类型的值可以转换为另一种类型的值的过程,没有演员表操作员。

该标准精确地指定了可以应用隐式转换的上下文;必须始终有一个目标类型。例如,在代码int x = Y; 中,表达式Y 可以是某种不是int 的类型,但它定义了到int 的隐式转换。

除了default argument promotions 之外,没有对原型的... 部分对应的函数参数应用隐式转换。对于指针值,默认参数提升保持不变。

您的问题的一个共同点似乎是编译器应该以某种方式假装execl 的行为就像最后一个参数有一个原型一样。但实际上没有,编译器对特定函数没有任何神奇的行为。你通过的就是你得到的。


  1. 标准规定表达式(char *)0 的值为空指针。它没有说明空指针的表示,并且可能有多个不同的表示都是空指针。

  2. execl 函数规范说参数列表应该以(char *)0 结尾,它是char * 类型的值。 void * 类型的值不是 char * 类型的值,并且如上所述在此上下文中没有隐式转换。

  3. 仍然没有隐式转换;您引用的文字是说您可以在这种特定情况下使用错误的类型参数(没有原型参数;预计char *,但提供void *,反之亦然)。

  4. 那将是未定义的行为,您在第 3 点中引用的文本不适用于int *

  5. sigaction 函数有原型;有问题的参数是struct sigaction *oldact。当您尝试使用不同类型的值初始化原型参数(或任何变量)时,会尝试隐式转换为参数的类型。存在从任何空指针值到不同类型的空指针值的隐式转换。此规则在 C11 6.3.2.3/4 中。这样代码就OK了。

【讨论】:

  • @EricPostpischil 尽管可以作为函数参数互换,但它们具有不同的类型...... OP的问题2不是很清楚,我会尝试改写我对那部分的回答
  • 标准规定表达式(char *)0 的值为空指针。 是吗? 6.3.2.3 Pointers, p3 声明“一个值为 0 的整数常量表达式,或这种类型转换为 void * 的表达式,称为空指针常量。......”我不明白 ( char * ) 0 是如何适合那里的。这是一个值为 0 的整数常量表达式,但它被强制转换为 char *
  • @AndrewHenle 你把 null pointer constantnull pointer 混为一谈了。 (char *)0 是一个空指针和一个地址常量,但不是一个空指针常量
  • 这就是我的观点 - (char *)0 不是空指针常量。 6.3.2.3p3 的第二部分开始“如果将空指针常量转换为指针类型......”。由于(char *)0 不是空指针常量,它是如何成为空指针的呢?我会说这不是获取空指针的严格一致的方法,尽管它可能会起作用。尽管我对它实际上是一个空指针的解释持开放态度,因为 C 标准的那部分的意图是验证假设 0 是空指针的糟糕代码。
  • @AndrewHenle 演员表是一种显式转换。强制转换运算符的操作数被转换为括号中的类型。 (C11 6.5.4/5)。您引用的文本并非无关紧要,您似乎又将 null pointer constantnull pointer 混淆了。 (void *)0 是一个空指针和一个空指针常量。 (char *)0 是空指针,而不是空指针常量。
【解决方案2】:

从 C99 开始,the specification of va_arg 部分读取

如果[作为参数传递给va_arg的类型]与实际下一个参数的类型不兼容(根据默认参数提升),则行为未定义,但以下情况除外:

  • 一种是有符号整数类型,另一种是对应的无符号整数类型,值在两种类型中都可以表示;
  • 一种类型是指向 void 的指针,另一种是指向字符类型的指针。

第二个要点意味着,对于任何使用va_arg 访问其参数的可变参数函数,调用形式为

variadic_function("a", "b", "c", (void *)0);

任何时候都有效

variadic_function("a", "b", "c", (char *)0);

本来可以的。

不幸的是,有一个问题:我找不到任何要求可变参数标准库函数1 [表现得好像他们] 通过向va_arg 拨打一系列电话。你可能在想,好吧,他们还能怎么做呢?在实践中它是va_arg 或手写汇编语言,也许委员会不想要求手写汇编语言完全等效,但我不会担心。

所以你引用的书在技术上是不正确的。但是,我还是会写

execl(prog, arg, (char *) NULL);

如果我首先要使用 NULL(我通常更喜欢使用 0 作为空指针常量),因为您不应该编写 依赖 NULL 扩展为的代码((void *)0),和

execl(prog, arg, 0);

无疑是不正确的。例如,execlint 为 32 位、char * 为 64 位且int 数量没有符号或零扩展为 64 的任何 ABI 上都不会从该 0 接收空指针作为变量参数列表的一部分传递时的位。


1execl 不是 C 标准的一部分,但它是 POSIX 标准的一部分,任何首先提供execl 的系统都是可能至少符合 POSIX 的一个子集。可以假设 C 标准的所有clause 7.1.4 也适用于 POSIX 指定的函数。

【讨论】:

    【解决方案3】:

    1)C标准是否要求空指针与(char*) 0一样表示

    是的,因为空指针常量的类型为 void *,并且因为 void *char * 具有相同的表示形式。

    这在C standard的第6.3.2.3p3节中有详细说明:

    值为 0 的整数常量表达式,或者这样的 将表达式转换为 void * 类型,称为空指针常量。

    以及第 6.2.5p28 节:

    指向 void 的指针应具有相同的表示形式和 对齐要求作为指向字符类型的指针。 48)

    ...

    48) 相同的表示和对齐要求是 意味着可互换性作为函数的参数, 从函数和联合成员返回值。


    2) (void *) 0 不是某个类型的空指针吗? 如果是,为什么不能 编译器将 execl(prog, arg, (void*) 0) 中的 (void *) 0 转换为 "a null 适当类型的指针”?

    它是一个类型的空指针,该类型是void *

    execl的定义是:

    int execl(const char *path, const char *arg, ...);
    

    因此它不能将第三个参数转换为适当的类型,因为它不知道适当的类型是什么,但这并不重要,因为 void *char * 在 6.2.4p28 和脚注中是可互换的48 如上所述。

    3) 编译器能否将(void *) 0 转换为execl(prog, arg, (void*) 0) 现在到“适当类型的空指针”? 为什么它与我第 2 点中的引用相矛盾?

    它仍然无法转换,因为它不知道合适的类型是什么。但同样,这并不重要,因为 void *char * 可以互换。

    4) 如果我将 execl(prog, arg, (void*) 0) 中的 (void *) 0 替换为 0 到任何类型的指针,例如(int *) 0,编译器能否将execl(prog, arg, (int*) 0) 中的(int *) 0 转换为“适当类型的空指针”?

    不,因为它也不知道合适的类型是什么。在这种情况下,如果 int *char * 没有相同的表示,您可能会遇到问题。

    5) 对于非可变函数调用,例如在sigaction(SIGINT, &sa, (int*) 0) 中,编译器可以将(int *) 0 转换为“适当类型的空指针”吗?

    是的,因为(int *)0 是一个空指针,而且空指针可以转换为任何其他指针。

    【讨论】:

    • "因为空指针常量的类型为 void *," - 0 也是一个空指针常量,类型为 int
    • @EricPostpischil IMO C11 6.2.5p28(带脚注 48)试图说 char *void * 在这种情况下可以互换使用。我想不出这句话有任何其他可能的含义或解释。
    • @EricPostpischil 我个人将遵循 6.2.5p28 所表达的意图,毕竟该标准的目的是编纂一组预期的规则,我认为很明显它们旨在互换性在这里,即使 6.5.2.2/6 的措辞出现矛盾。
    • C89 中没有,但在 C99 和 C11 中,va_arg 的定义包含一个语句,当调用者通过 void * 时,特别允许 va_arg (ap, char *),反之亦然 (@987654322 @,第二个要点)。这足以让我认可 M.M 的解释。然而,我认为,在这种情况下应该写(char *)0(char *)NULL,只是因为人们不应该依赖具有特定定义的NULL。
    • 是的,因为(int *)0是一个空指针 是吗?根据6.3.2.3, p3:“值为0 的整数常量表达式,或转换为void * 类型的表达式称为空指针常量。...”(int *)0 不是(void *)0
    【解决方案4】:

    AFAI 了解 default argument promotion 应用于var-args functions,除了指针,它们保持原样(zwol 的示例也支持1)。因此,当 0 传递给有点 var-args 函数时,例如exec() family,它被识别为朴素的整数0 代替空指针。顺便说一句,execl(prog, arg, 0); 可以在空指针的内部表示和整数 0 的内部表示相同但不是强制性的系统上工作。

    execl(prog, arg, NULL); 也可能会在以下情况下无意中工作

    • 如果NULL在系统上定义为整数0,上述 应用了解释。
    • 如果在系统上将NULL 定义为空指针常量,则为 (void*)0,虽然在可变参数函数中,编译器 不能转换 (void*)0 指向适当类型的空指针,即使从 C 标准说char*void* 必须具有相同的内部 代表。

    更多信息,请查看here。 来自here 的其他示例。


    1 例如,execlint 是 32 位、char * 是 64 位和 int 的任何 ABI 上都不会从该 0 接收空指针当作为变量参数列表的一部分传递时,数量不会符号或零扩展到 64 位。

    【讨论】:

      猜你喜欢
      • 2011-10-23
      • 2017-10-07
      • 2021-09-20
      • 2015-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      相关资源
      最近更新 更多