【发布时间】:2018-07-01 06:00:43
【问题描述】:
在 ISO C 之前的旧时代,以下代码不会让任何人感到惊讶:
struct Point {
double x;
double y;
double z;
};
double dist(struct Point *p1, struct Point *p2) {
double d2 = 0;
double *coord1 = &p1.x;
double *coord2 = &p2.x;
int i;
for (i=0; i<3; i++) {
double d = coord2[i] - coord1[i]; // THE problem
d2 += d * d;
return sqrt(d2);
}
当时,我们都知道双精度对齐允许编译器在struct Point 中不添加任何填充,我们只是假设指针算法可以完成这项工作。
不幸的是,这条有问题的行使用了指针算法(p[i] 是定义 *(p + i))在任何数组之外,这是标准明确不允许的。 C11 的 n1570 草案在 6.5.6 加法运算符§8 中说:
当一个整数类型的表达式被添加到指针指针中或从指针指针中减去时, 结果具有指针操作数的类型。 如果指针操作数指向一个元素 一个数组对象,并且数组足够大,结果指向一个元素的偏移量 原始元素使得结果和原始的下标的差异 数组元素等于整数表达式...
当我们没有同一个数组的两个元素时什么都不说,标准未指定它并从那里未定义的行为(即使所有常见的编译器都对此感到高兴......)
问题:
由于这个习惯用法允许避免代码复制只更改x 和y 然后z 这很容易出错,因此浏览结构的元素就像它们是同一个数组?
免责声明:显然它只适用于相同类型的元素,并且可以使用简单的static_assert 来检测填充,如我的that other question 所示,因此填充、对齐和混合类型不是 我的问题在这里。
【问题讨论】:
-
请注意,即使在 ISO C 之前的日子里,“双精度对齐允许编译器不添加任何填充”与“双精度对齐要求编译器避免添加任何填充”。即使在那个时候,依赖后者的代码也依赖于编译器的实现。我确信它工作可靠,而且它可能仍然工作可靠,但它不是正式未定义行为的原因是 C 的行为一开始还没有完全形式化。
-
@JohnBollinger:我知道,这就是为什么我说它会让任何人感到惊讶而不是它曾经是法律代码 ...
-
这里是 ISO C 之前的人。不,我不会感到惊讶。我会要求犯罪者使用适当的索引变量来代替不可维护的 x、y 和 z 的临时集合。话虽如此,我会毫不犹豫地将
#define x coord[0]之类的东西添加到当时的固定代码库中(但现在不是)。也许enum { x = 0, y = 1, z = 2 }和point.coord[x]等
标签: c arrays struct language-lawyer