【发布时间】:2014-12-29 23:00:12
【问题描述】:
C99 标准规定:
当两个指针相减时,两个指针都指向同一个数组对象的元素,或者指向数组对象最后一个元素的元素
考虑以下代码:
struct test {
int x[5];
char something;
short y[5];
};
...
struct test s = { ... };
char *p = (char *) s.x;
char *q = (char *) s.y;
printf("%td\n", q - p);
这显然违反了上述规则,因为p 和q 指针指向不同的“数组对象”,并且根据规则,q - p 的区别是未定义的。
但在实践中,为什么这样的事情会导致未定义的行为?毕竟,结构成员是按顺序排列的(就像数组元素一样),成员之间有任何潜在的填充。确实,填充量会因实现而异,这会影响计算的结果,但为什么该结果应该是“未定义的”?
我的问题是,我们可以假设标准只是“无知”这个问题,还是有充分的理由不扩大这条规则?上述规则不能改写为“都应该指向同一个数组对象的元素或同一个结构的成员”吗?
我唯一的怀疑是分段内存架构,其中成员可能最终位于不同的段中。是这样吗?
我还怀疑这就是 GCC 定义自己的 __builtin_offsetof 的原因,以便对 offsetof 宏进行“符合标准”的定义。
编辑:
正如已经指出的,标准不允许对 void 指针进行算术运算。它是一个 GNU 扩展,仅当 GCC 通过-std=c99 -pedantic 时才会触发警告。我正在用char * 指针替换void * 指针。
【问题讨论】:
-
并且在您的示例中,无论如何都禁止对 void 进行指针运算。如果类型不一样,你怎么能减去它们?
-
您是正确的,标准不允许对 void 指针进行算术,它是 GNU 扩展。想象一下,两个指针都是
char *。 -
gcc 允许在
void*上进行指针运算,方法是将sizeof(void)视为 1(与char*相同)。因此,就您的问题而言,这没有区别。 -
这是允许边界检查实现所必需的,也就是说,afaik,标准委员会的意图(或者至少是 C89 的意图)允许它。我相信实现检查边界可以(必须)可靠地捕捉到这种情况(也就是说,它是 UB,尽管它在现实中有效)。不过,这样的实现会破坏很多现有的代码。而且标准对对象的概念有点模糊,因此很难得到准确的答案。
-
@mafso:在没有别名规则的情况下,每个对象都可以被视为联合的成员,该联合包含可能占用空间的每种类型的成员。给定
struct {int x, y;} foo;,如果对于某个整数值n,foo.y的地址等于((int*)&foo)+n,那么&foo.x和&foo.y将是整数数组中元素0 和n的地址从地址&foo开始。不幸的是,标准的作者粗心地引入了别名规则,这些规则依赖于他们从未定义过的“对象”的细节,因为语言不需要它们。