在 sizeof 操作中解引用空指针是否有效?

26

我发现了一段代码,按照我的理解它应该会因为分段错误(segmentation fault)而崩溃,但是它却毫无问题地运行。下面是相关的数据结构以及代码(其中注释位于正上方):

typedef struct {
  double length;
  unsigned char nPlaced;
  unsigned char path[0];
}


RouteDefinition* Alloc_RouteDefinition()
{
  // NB: The +nBags*sizeof.. trick "expands" the path[0] array in RouteDefinition
  // to the path[nBags] array
  RouteDefinition *def = NULL;
  return (RouteDefinition*) malloc(sizeof(RouteDefinition) + nBags * sizeof(def->path[0]));
}

为什么这个能够工作?我了解到 sizeof char* 会根据特定体系结构解析为指针的大小,但是在解引用 NULL 指针时难道它不应该崩溃吗?


请不要在C语言中将malloc()的返回值强制转换。 - unwind
Joachim的正确性(+1)。虽然sizeof可能是编译器内部的,但您通常可以通过查看标准库的offsetof实现来观察这种有趣且具体的语言行为:它可能会获取一个由强制转换0 / NULL指针而成的虚构对象的数据成员的地址... 这甚至比sizeof更接近边缘,但完全合法。 - Tony Delroy
sizeof(def->path[0]) 的定义是 1,因此返回语句可以简化为更易读的形式:return malloc(sizeof(RouteDefinition) + nBags); - Klas Lindbäck
3个回答

20
这是为什么?
这是因为sizeof是一个编译时构造,除了可变长度数组外根本不被评估。如果我们查看C99草案标准6.5.3.4The sizeof operator2段中说(我强调):

[...]大小是从操作数的类型确定的。结果是一个整数。如果操作数的类型是可变长度数组类型,则对操作数进行评估;否则,不对操作数进行评估,结果是整数常量。

我们还在第5段中看到以下示例,证实了这一点:
double *dp = alloc(sizeof *dp);
       ^^^                ^
                          |                                 
                          This is not the use of uninitialized pointer 

在编译时,为了计算结果,将确定表达式的 类型。我们可以通过以下示例进一步说明:

int x = 0 ;
printf("%zu\n", sizeof( x++ ));

这不会增加x,这非常好。

更新

正如我在我的回答中提到的为什么sizeof(x ++)不会增加x?sizeof是一个编译时操作的例外情况,当它的操作数是可变长度数组(VLA)时。虽然我之前没有指出,但上面来自6.5.3.4的引用确实如此。

尽管在C11中与C99不同,在这种情况下是否评估sizeof是未指定的。

另外,请注意,这里有一个C ++版本的问题:如果在C ++中不评估应用sizeof的表达式,是否合法在sizeof内解除引用空或无效指针?


10

sizeof 运算符是一个纯粹的编译时操作。在运行时不执行任何操作,这就是为什么它可以正常工作的原因。

顺便说一下,path 成员实际上不是一个指针,所以它不能严格意义上的为 NULL


9
对于 VLAs,sizeof 不是编译时确定的例外情况。 - Shafik Yaghmour

3
声明sizeof是一个纯粹的编译时构造(如当前的答案)并不完全准确。自C99以来,sizeof不再是纯粹的编译时构造。如果操作数类型是VLA,则sizeof的操作数在运行时进行评估。到目前为止所发布的答案似乎忽略了这种可能性。
您的代码很好,因为它不涉及任何VLA。然而,像这样的东西可能会有不同的情况。
unsigned n = 10;
int (*a)[n] = NULL; // `a` is a pointer to a VLA 

unsigned i = 0;
sizeof a[i++];      // applying `sizeof` to a VLA

根据C99标准,sizeof的参数应该被评估(即i应该被递增,参见https://ideone.com/9Fv6xC)。但是,我不确定在这里a[0]的空指针解引用是否应该产生未定义的行为。

有没有任何情况需要从 sizeof 的参数中要求任何可观察的行为来提高语言的表现力?唯一可能影响 sizeof 结果的值计算情况是当它应用于在 sizeof 表达式内创建的 VLA 类型时,我想不出任何允许这样做会增强语言表现力的原因。顺便说一下,即使是 typedef 语句也可以生成可执行代码! - supercat
@supercat “即使是typedef语句也可以生成可执行代码!”。你能举个例子吗?谢谢! - a3f
@supercat “即使是typedef语句也可以生成可执行代码!”。你能举个例子吗?谢谢! - pmor
@pmor:好的。给定声明 int doSomething(void);,块作用域声明 typedef int silliness[doSomething()]; 将调用 doSomething() - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接