在以前的 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);
}
那时,我们都知道 double 的对齐允许编译器在 struct Point
中添加任何填充,我们只是假设指针算法可以完成这项工作 .
不幸的是,这个有问题的行使用了指针算法(p[i]
根据定义 *(p + i)
)在标准明确不允许的任何数组之外。 C11 的 n1570 草案在 6.5.6 additive operators §8 中说:
When an expression that has integer type is added to or subtracted from a pointerpointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression...
当我们没有同一个数组的两个元素时什么也没有说,它没有被标准指定,并且从那里开始未定义的行为(即使所有常见的编译器都对此感到高兴......)
问题:
因为这个习语允许避免代码复制只改变 x
和 y
然后 z
这是很容易出错的,什么可以是一致的如何浏览结构的元素,就好像它们是同一数组的成员一样?
免责声明:它显然只适用于相同类型的元素,可以使用简单的 static_assert
检测填充,如 that other question 所示。我的问题,所以填充、对齐和混合类型不是我的问题。
最佳答案
C 没有定义任何方式来指定编译器不得在 struct Point
的命名成员之间添加填充,但是许多编译器都有一个扩展可以提供这种填充。如果你使用这样的扩展,或者如果你只是愿意假设没有填充,那么你可以使用一个带有匿名内部 struct
的 union
,比如所以:
union Point {
struct {
double x;
double y;
double z;
};
double coords[3];
};
然后您可以通过它们各自的名称或通过 coords
数组访问坐标:
double dist(union Point *p1, union Point *p2) {
double *coord1 = p1->coords;
double *coord2 = p2->coords;
double d2 = 0;
for (int i = 0; i < 3; i++) {
double d = coord2[i] - coord1[i];
d2 += d * d;
}
return sqrt(d2);
}
int main(void) {
// Note: I don't think the inner braces are necessary, but they silence
// warnings from gcc 4.8.5:
union Point p1 = { { .x = .25, .y = 1, .z = 3 } };
union Point p2;
p2.x = 2.25;
p2.y = -1;
p2.z = 0;
printf("The distance is %lf\n", dist(&p1, &p2));
return 0;
}
关于c - 以一致的方式别名结构和数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48384399/