C空指针算术

gat*_*sec 3 c standards pointers pointer-arithmetic language-lawyer

我注意到Clang的这个警告:

warning: performing pointer arithmetic on a null pointer
has undefined behavior [-Wnull-pointer-arithmetic]
Run Code Online (Sandbox Code Playgroud)

详细信息,正是此代码触发此警告:

uint8_t *end = ((uint8_t*)0) + sizeof(uint8_t) * count;
Run Code Online (Sandbox Code Playgroud)

为什么在从不同于零的整数获得的非空指针上执行相同操作时禁止对空指针进行算术运算不会触发任何警告?

更重要的是,C标准是否明确禁止空指针算法

Sto*_*ica 6

C标准不允许.

6.5.6加法运算符(强调我的)

8当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型.如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式.换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第th个元素,只要它们存在即可.此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素.如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数.

出于上述目的,指向单个对象的指针被视为指向1个元素的数组.

现在,((uint8_t*)0) 指向数组对象的元素.仅仅因为持有空指针值的指针不指向任何对象.对此说:

6.3.2.3指针

3如果将空指针常量转换为指针类型,则保证将结果指针(称为空指针)与指向任何对象或函数的指针进行比较.

所以你不能对它进行算术运算.警告是合理的,因为正如第二个突出显示的句子所提到的那样,我们处于未定义的行为的情况.

不要被offsetof宏可能像这样实现的事实所迷惑.标准库不受用户程序约束的约束.它可以运用更深入的知识.但是在我们的代码中这样做并没有明确定义.

  • @Prion - 我也已经解决了。编译器知道托管实现中的标准库提供了 `offsetof` 并且你不能重新定义它,因为有 UB 的风险。所以它假设它来自库(因为我们当然都编写 UB 免费程序)并且不会抱怨。如果您仍然不满意,“提问”按钮始终可供您使用。我回答了*完全*您提出的问题。 (3认同)
  • @Prion - 我觉得我们在兜圈子。关于警告的编译器设计的哲学方面对标准中的规范文本有 0 影响。 (2认同)
  • @PaulOgilvie - 不,不是*任何*编译器实现者。GCC 使用 `__builtin_offsetof` 避免了这个问题。 (2认同)
  • https://en.wikipedia.org/wiki/Offsetof。每当我检查 `offsetof` 宏时,它都被定义为 `((size_t)&(((st *)0)->m))`。这很好,因为它并没有真正取消引用空指针,而是评估为常量值。此外,声明某些行为是未定义行为的规范与为此类行为提供有用结果的编译器实现之间也存在巨大差异。 (2认同)