编译器能否将execl(prog,arg,(void *)0)中的(void *)0转换为适当类型的空指针?

时间:2018-09-06 01:31:20

标签: c pointers posix null-pointer

从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是否不是类型的空指针?

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

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

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

    为什么它与我的观点2中的引​​用矛盾?

  5. 如果我将(void *) 0中的execl(prog, arg, (void*) 0)替换为0到任何类型的指针(例如(int *) 0),则编译器可以将{{1 }}为“适当类型的空指针”? 谢谢。

  6. 对于非变量函数调用,例如在(int *) 0中,编译器可以将execl(prog, arg, (int*) 0)强制转换为“适当类型的空指针”吗?

谢谢。

4 个答案:

答案 0 :(得分:8)

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

我假设当您谈论“编译器强制转换”时,您的意思是谈论隐式转换,即将一种类型的值转换为另一种类型的值的过程,没有强制转换运算符。

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

default argument promotions之外,没有隐式转换应用于与原型的...部分相对应的函数参数。对于指针值,默认参数Promotions使其保持不变。

您的问题的一个共同点似乎是,编译器应以某种方式假装execl的行为就像最后一个参数已存在原型一样。但是实际上没有,并且编译器没有特定功能的任何魔术行为。通过的就是获得的。


  1. 该标准指定表达式(char *)0的值为空指针。它没有提到任何有关空指针的表示,并且可能有多个不同的表示形式都是空指针。

  2. execl函数规范指出,参数列表应以(char *)0终止,char *是类型void *的值。类型char *的值不是类型char *的值,如上所述,在此上下文中没有隐式转换。

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

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

  5. struct sigaction *oldact函数有一个原型;该参数为class Demo { string text; public: void Text(const string& updatedText) { text = updatedText; } const string& Text() const { return text; } }; 。当您尝试使用不同类型的值初始化原型参数(或任何变量)时,将尝试隐式转换为参数的类型。从任何空指针值到其他类型的空指针值都有隐式转换。此规则在C11 6.3.2.3/4中。这样代码就可以了。

答案 1 :(得分:4)

  

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

相同

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

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

  

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

第6.2.5p28节:

  

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

     

...

     

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


  

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

它是一种类型的空指针,并且该类型是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)如果我将(void *) 0中的execl(prog, arg, (void*) 0)替换为   到(int *) 0之类的任何类型指针的编译器是否可以将(int *) 0中的execl(prog, arg, (int*) 0)强制转换为“适当类型的空指针”?

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

  

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

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

答案 2 :(得分:4)

自C99起,the specification of va_arg会部分读取

  

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

     
      
  • 一种类型是有符号整数类型,另一种类型是相应的无符号整数类型,并且值在两种类型中均可表示;
  •   
  • 一种类型是指向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);

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


1 execl不是 C 标准的一部分,但它是POSIX标准的一部分,并且任何在其中提供execl的系统首先可能至少与POSIX的子集兼容。可以假定所有C标准的clause 7.1.4都适用于POSIX指定的功能。

答案 3 :(得分:0)

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定义为null-pointer-constant,则为 (void*)0,尽管在可变函数中编译器无法转换 (void*)0指向适当类型的空指针,即使C 标准说char*void*必须具有相同的内部 表示形式。

有关更多信息,请浏览here。 来自here的其他示例。


1 例如,execl不会从0为32位int的任何ABI上的char *接收空指针是64位,并且int数量作为变量参数列表的一部分传递时不会对64位进行正负号或零扩展。