编译器可以将`excel(proc,arg,(void *)0)`中的`(void *)0`转换为适当类型的空指针吗?

问题描述 投票:5回答:4

来自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 *类型的指针需要具有相同的内部表示。这意味着在(void *) 0的示例情况下,传递(char *) 0而不是execl()不会成为问题”。 编译器现在可以将(void *) 0中的execl(prog, arg, (void*) 0)转换为“适当类型的空指针”吗? 为什么它与我的观点2中的引​​用相矛盾?
  4. 如果我用(void *) 0中的execl(prog, arg, (void*) 0)替换任何类型的指针(例如(int *) 0),编译器可以将(int *) 0中的execl(prog, arg, (int*) 0)转换为“相应类型的空指针”吗?谢谢。
  5. 对于非变量函数调用,例如在sigaction(SIGINT, &sa, (int*) 0)中,编译器可以将(int *) 0转换为“适当类型的空指针”吗?

谢谢。

c pointers posix null-pointer
4个回答
8
投票

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

我将假设当你谈到“编译器转换”时,你的意思是谈论隐式转换,这是一种类型的值可以转换为另一种类型的值而没有转换运算符的过程。

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

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

您的问题的一个共同点似乎是编译器应该以某种方式假装execl表现得好像有最后一个参数的原型。但实际上没有,并且编译器对特定函数没有任何魔术行为。你传递的是你得到的。


  1. 该标准指定表达式(char *)0的值是空指针。它没有说明空指针的表示,并且可能有多个不同的表示都是空指针。
  2. execl函数规范说,参数列表应该由(char *)0终止,char *void *类型的值。类型为char *的值不是char *类型的值,并且在此上下文中没有如上所述的隐式转换。
  3. 仍然没有隐含的转换;你引用的文字说你可以在这个特定情况下使用错误的类型参数(没有原型参数;而void *预期但int *提供,反之亦然)。
  4. 这将是未定义的行为,您在第3点引用的文本不适用于sigaction
  5. struct sigaction *oldact函数有一个原型;有问题的参数是(char*) 0。当您尝试使用不同类型的值初始化原型参数(或任何变量)时,将尝试隐式转换为参数类型。存在从任何空指针值到不同类型的空指针值的隐式转换。该规则在C11 6.3.2.3/4中。所以代码没问题。

4
投票

1)C标准是否要求空指针与void *相同

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

这在(void *) 0的6.3.2.3p3节中有详细说明:

值为0的整型常量表达式或类型为void *的表达式称为空指针常量。

第6.2.5p28节:

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

...

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


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

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

int execl(const char *path, const char *arg, ...); 的定义是:

void *

因此它不能将第三个参数转换为适当的类型,因为它不知道适当的类型是什么,但它无关紧要因为char *(void *) 0可以按照上面提到的6.2.4p28和脚注48进行互换。

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

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

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

不,因为它再次不知道适当的类型是什么。在这种情况下,如果char *sigaction(SIGINT, &sa, (int*) 0)没有相同的表示,则可能会出现问题。

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

是的,因为the specification of va_arg是一个空指针,并且因为空指针可以转换为任何其他指针。


4
投票

自C99以来,va_arg部分阅读

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

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

第二个要点意味着,对于使用variadic_function("a", "b", "c", (void *)0); 访问其参数的任何可变函数,调用表单

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

什么时候有效

va_arg

本来可以。

不幸的是,有一个问题:我找不到对可变标准库函数1的任何要求,以[通过对va_arg进行一系列调用来表示 - 如果它们]访问它们的参数。你可能在想,好吧,他们还会怎么做呢?在实践中它是execl(prog, arg, (char *) NULL); 或手写汇编语言,也许委员会不希望要求手写汇编语言完全等同,但我不担心它。

因此,您引用的书在技术上是不正确的。但是,我还是会写的

0

如果我打算首先使用NULL(我通常更喜欢使用((void *)0)作为空指针常量),因为你不应该编写依赖于NULL扩展到execl(prog, arg, 0); 的代码,并且

execl

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


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


0
投票

AFAI明白var-args functions适用于0,除了指针,它们保持原样(zwol的例子也支持1)。所以当exec() family被传递给有点var-args函数时,例如0,它被认为是朴实的整数execl(prog, arg, 0);而不是空指针。 En passant,0可以在空指针的内部表示和整数execl(prog, arg, NULL);的内部表示相同的系统上工作,但它不是强制性的。

NULL也可能无意中工作,但须遵守以下任何一项规定

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

更多信息,请看一下qazxsw poi。来自qazxsw poi的另外一个例子。


1例如,here在任何ABI上都不会从execl接收空指针,其中0是32位,int是64位,并且char *数量在作为变量参数的一部分传递时没有符号或零扩展到64位名单。

© www.soinside.com 2019 - 2024. All rights reserved.