【问题标题】:Why can this C code run correctly? [duplicate]为什么这段 C 代码可以正确运行? [复制]
【发布时间】:2023-03-24 01:25:01
【问题描述】:

C 代码是这样的:

 #include <stdio.h>
 #include <unistd.h> 
 #define DIM(a) (sizeof(a)/sizeof(a[0])) 
 struct obj
 {
     int a[1];
 };
 int main()
 {
     struct obj *p = NULL;
     printf("%d\n",DIM(p->a));
     return 0;
 }

这个对象指针pNULL,所以,我认为这个p-&gt;a 是非法的。 但是我在Ubuntu14.04中测试过这段代码,它可以正确执行。所以,我想知道为什么......


注意:原始代码上面有int a[0],但我已将其更改为int a[1],因为每个人似乎都被这个问题而不是实际问题所困扰,即:

p 等于NULL 时,表达式sizeof(p-&gt;a) 是否有效?

【问题讨论】:

  • 不是这里发生的事情,但不要假设“非法”的事情(未定义的行为)似乎不起作用。事实上,在编译器版本升级这样简单的事情破坏它们之前,它们似乎可以工作很长时间。
  • Chris Beck 是绝对正确的:你的#define DIM() 宏是编译时间。这里只有 type 很重要:而不是实际的运行时值。
  • @paxdiablo 现在是 2015 年,是时候停止将 C++ 视为 C 的超集了吗?
  • @paxdiablo 除了int a[0]; 在两种语言中都无效之外,所有这些代码都是有效的 C++。 OP 可能正在使用 C++ 编译器,我们不知道
  • @this: OMG,太搞笑了 :-) 这个问题实际上与零长度数组无关,尽管我承认考虑到 cmets 中发生的侧向跟踪,它看起来可能是这样。问题只是与sizeof(p-&gt;a)p == NULL 时是否有效有关。事实上,考虑到造成的混乱,我想我会去编辑那个零。

标签: c


【解决方案1】:

因为sizeof 是编译时构造,它不依赖于评估输入。 sizeof(p-&gt;a) 仅根据成员 p::a 的声明类型进行评估,并成为可执行文件中的常量。所以 p 指向 null 的事实没有任何区别。

p 的运行时值在 sizeof(p-&gt;a) 表达式中绝对没有任何作用。

在 C 和 C++ 中,sizeof 是一个运算符,而不是一个函数。它可以应用于 type-idexpression。除了表达式是可变长度数组(C99 中的新功能)(如 paxdiablo 所指出的那样)的情况外,表达式是 未计算的操作数,结果与如果您已将 sizeof 与该表达式的 type 相对应。 (C.f. C11 引用由于下面的 paxdiablo,C++14 工作草案 5.3.3.1)

【讨论】:

  • 另外值得指出的是sizeof 是一个操作符,而不是一个函数。可能会缓解一些困惑。
  • 长度为零的数组在标准 C 中是非法的,但在 GNU C 中是允许的(这与标准 C 明显不同)。灵活的数组成员不算数——维度不是 0,而是未指定。
  • sizeof(p)sizeof((struct obj))有没有区别?无论 p 的值是多少。
  • sizeof 是编译时,除非您采用 VLA 类型的大小。
  • @Yang.fr 好吧,sizeof(*p)sizeof(obj) 没有区别。 sizeof(p)sizeof(obj*) 相同
【解决方案2】:

首先,如果您想要真正可移植的代码,您不应该尝试创建一个大小为零的数组1,就像您在原始问题中所做的那样,现在已修复。但是,由于它与您关于sizeof(p-&gt;a)p == NULL 时是否有效的问题并不真正相关,我们现在可以忽略它。

来自C11部分6.5.3.4 The sizeof and _Alignof operators(我的粗体字):

2/ sizeof 运算符产生其操作数的大小(以字节为单位),它可以是表达式或带括号的类型名称。 大小由操作数的类型决定。结果是一个整数。如果操作数的类型是变长数组类型,则计算操作数;否则,不计算操作数,结果是一个整数常量。

因此,除非它是一个可变长度数组(您的示例不是),否则不会对操作数进行评估。只有类型本身用于计算大小。


1 对于那里的语言律师,C11 在6.7.6.2 Array declarators 中声明(我的粗体字):

1/ 除了可选的类型限定符和关键字static[] 可以分隔表达式或*。如果它们分隔了一个表达式(它指定了一个数组的大小),则该表达式应为整数类型。如果表达式是常量表达式,它的值应大于零。

但是,由于这是在 约束 部分(其中 shallshall not 不涉及未定义的行为),这仅意味着程序本身并不严格符合。它仍然被标准本身所涵盖。

【讨论】:

  • 但是长度为零的数组在标准 C 中是非法的,尽管 GCC 允许它们作为扩展。灵活的数组成员不算数——维度不是 0,而是未指定。
  • @Jonathan,我不确定这与问题本身有关,因为您可以通过简单地将 0 转换为 1 来解决它,并且问题仍然有效,因为它是当pNULL 时,sizeof(p-&gt;a) 是否非法。不过,为了完整起见,我会将其包含在答案中。
  • 结构声明是非法的——无论它是否用于任何用途。包含该结构类型的程序不是符合标准的 C。
  • @Jonathan,我不同意那里,标准允许不符合。它特别提到了符合、严格符合和不符合的程序和实现,并允许实现符合要求,前提是它们的扩展不会改变严格符合程序的行为。因此,虽然代码不是严格符合的声明是正确的,但 并不 意味着根据标准它是非法的 C。
  • @Yang.fr int x = rand() % 10 + 1; int p[x];
【解决方案3】:

此代码在 ISO C 中包含违反约束的原因:

struct obj
{
    int a[0];
};

零大小的数组在任何地方都是不允许的。因此,C 标准没有定义该程序的行为(尽管there seems to be some debate 关于这一点)。

只有在编译器实现非标准扩展以允许零大小数组时,代码才能“正确运行”。

扩展必须被记录(C11 4/8),所以希望你的编译器的文档定义它的行为 struct obj(一个零大小的结构?)和 sizeof p-&gt;a 的值,以及 sizeof 是否评估当操作数表示一个大小为零的数组时,它的操作数。

【讨论】:

  • 请注意,GCC 是一个编译器,它实现了一个非标准扩展以允许零大小的数组。
  • 也许(在阅读 cmets 到其他问题之后),您可能想要淡化“非法”位。与其说它是非法的,不如说它在严格遵守的程序中是无效的。如果实现不改变严格符合程序的行为,则特别允许实现提供扩展。因此,它们也被 ISO C 覆盖。并且在与UB无关的约束部分中限制零大小数组的位。
  • @paxdiablo 已更改措辞。在 C++ 中,约束违规是明确的 UB;但看起来 C 标准的作者选择了含糊其辞......
  • 在@paxdiablo 的回答下查看我的cmets。问题中的代码需要来自符合要求的编译器的诊断。如果编译器不符合标准,那么标准对编译器没有影响,它可以为所欲为。
  • @JonathanLeffler 是的,但是编译器在给出诊断后可以/必须做什么?
【解决方案4】:

sizeof() 不关心任何内容,它只查看表达式的结果类型。

由于C99variable length arrays,当可变长度数组是sizeof 操作数中表达式的一部分时,它会在运行时计算。否则,不会计算操作数并且结果是integer constant

structs 中的Zero-size array 声明从未被任何C standard 允许,但一些较旧的编译器在它成为编译器允许incomplete array declarations with empty brackets(flexible array members) 的标准之前允许它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 2023-03-18
    • 2014-08-31
    • 1970-01-01
    相关资源
    最近更新 更多