在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附加运营商§8中表示:
当指针指针中添加或减去具有整数类型的表达式时, result具有指针操作数的类型。 如果指针操作数指向的元素 一个数组对象,并且数组足够大,结果指向一个偏移的元素 原始元素使得结果和原始的下标不同 数组元素等于整数表达式...
当我们没有相同数组的两个元素时没有说什么,它没有被标准和未定义的行为指定(即使所有常见的编译器都很高兴...)
问题:
由于这个习惯用法允许避免代码复制只更改x
y
然后z
这很容易出错,所以可以采用一种一致的方式来浏览结构的元素如果他们是同一阵列的成员?
免责声明:它显然只适用于相同类型的元素,并且可以使用简单的static_assert
检测填充,如我的that other question所示,因此填充,对齐和混合类型不是我的问题在这里。
答案 0 :(得分:6)
C没有定义任何方式来指定编译器不能在struct Point
的命名成员之间添加填充,但是许多编译器都有一个扩展来提供它。如果您使用此类扩展,或者您只是愿意假设没有填充,那么您可以使用union
匿名内部struct
,如下所示:
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;
}
答案 1 :(得分:1)
这主要是对JohnBollinger的answer的补充。匿名结构成员允许使用干净整洁的语法,C将联合定义为由序列组成的类型 存储重叠的成员(6.7.2.1结构和联合说明符§6)。然后在6.5.2.3结构和联合成员中指定访问联合的成员:
3后缀表达式后跟。运算符和标识符指定的成员 结构或联合对象。该值是指定成员的值, 95),并且是左值if 第一个表达式是左值。
和(非规范但有益的)说明95准备:
95)如果用于读取union对象内容的成员与上次使用的成员不同 在对象中存储一个值,该值的对象表示的相应部分被重新解释 作为6.2.6中描述的新类型中的对象表示(有时称为''类型的过程) 双关语””)。这可能是陷阱表示。
这意味着对于当前版本的标准,在联合中的匿名结构成员的帮助下,数组对结构的别名是明确定义的行为。