别名结构和数组符合方式

在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中说:

当一个具有整数类型的表达式被添加到指针指针或从指针指针中减去时,结果具有指针操作数的类型。 如果指针操作数指向数组对象的元素,并且数组足够大 ,则结果指向与原始元素偏移的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。 ..

当我们没有相同数组的两个元素时没有说什么,它没有被标准和未定义的行为指定(即使所有常见的编译器都很高兴它…)

题:

因为这个成语允许避免代码复制只改变xy然后z这是非常容易出错的,所以可能是一种一致的方式来浏览结构的元素,就好像它们是同一个数组的成员一样?

免责声明:它显然只适用于相同类型的元素,并且可以使用简单的static_assert检测填充,如我的其他问题所示,因此填充,对齐和混合类型不是我的问题。

C没有定义任何方式来指定编译器不能在struct Point的命名成员之间添加填充,但是许多编译器都有一个扩展来提供它。 如果你使用这样的扩展,或者你只是愿意假设没有填充,那么你可以使用带有匿名内部structunion ,如下所示:

 union Point { struct { double x; double y; double z; }; double coords[3]; }; 

然后,您可以通过各自的名称或通过coords数组访问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; } 

这主要是JohnBollinger 答案的补充。 匿名结构成员允许使用干净整洁的语法,C将联合定义为由存储重叠的成员序列组成的类型 (6.7.2.1结构和联合说明符§6)。 然后在6.5.2.3 Structure和union成员中指定访问union的成员:

3后缀表达式后跟。 运算符和标识符指定结构或联合对象的成员。 该值是指定成员的值, 95)如果第一个表达式是左值,则该值是左值。

和(非规范但有益的)说明95准备:

95)如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新对象表示如6.2.6所述的类型(有时称为”punning”的过程)。 这可能是陷阱表示。

这意味着对于标准的当前版本,在联合中借助于匿名结构成员的数组对结构的别名是明确定义的行为。