以一致的方式别名结构和数组 [英] Aliasing struct and array the conformant way

查看:23
本文介绍了以一致的方式别名结构和数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 ISO C 之前的旧时代,以下代码不会让任何人感到惊讶:

struct Point {双x;双y;双z;};double dist(struct Point *p1, struct Point *p2) {双 d2 = 0;double *coord1 = &p1.x;double *coord2 = &p2.x;国际我;对于 (i=0; i<3; i++) {双 d = coord2[i] - coord1[i];//问题d2 += d * d;返回 sqrt(d2);}

当时,我们都知道 double 对齐允许编译器在 struct Point 中不添加填充,我们只是假设指针算术可以完成这项工作.

不幸的是,这个有问题的行使用了指针算法(p[i] 根据定义 *(p + i))在标准明确不允许的任何数组之外.C11 的 n1570 草案在 6.5.6 加法运算符 §8 中说:

<块引用>

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

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

问题:

由于这个习语允许避免代码复制只改变 xy 然后 z 这很容易出错,什么可能是一致的浏览结构元素的方法,就好像它们是同一数组的成员一样?

免责声明:它显然只适用于相同类型的元素,可以使用简单的 static_assert 检测填充,如 我的另一个问题,所以填充、对齐和混合类型不是我的问题.

解决方案

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

联合点{结构{双x;双y;双z;};双坐标[3];};

然后您可以通过它们的个人名称或通过 coords 数组访问坐标:

double dist(union Point *p1, union Point *p2) {double *coord1 = p1-> coords;double *coord2 = p2-> coords;双 d2 = 0;for (int i = 0; i <3; i++) {双 d = coord2[i] - coord1[i];d2 += d * d;}返回 sqrt(d2);}int main(void) {//注意:我不认为内大括号是必要的,但它们会沉默//来自 gcc 4.8.5 的警告:联合点 p1 = { { .x = .25, .y = 1, .z = 3 } };联合点 p2;p2.x = 2.25;p2.y = -1;p2.z = 0;printf("距离为 %lf\n", dist(&p1, &p2));返回0;}

In the old days of pre-ISO C, the following code would have surprized nobody:

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);
}

At that time, we all knew that alignment of double allowed the compiler to add no padding in struct Point, and we just assumed that pointer arithmetics would do the job.

Unfortunately, this problematic line uses pointer arithmetics (p[i] being by definition *(p + i)) outside of any array which is explicitely not allowed by the standard. Draft n1570 for C11 says in 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...

As nothing is said when we have not two elements of the same array, it is unspecified by the standard and from there Undefined Behaviour (even if all common compilers are glad with it...)

Question:

As this idiom allowed to avoid code replication changing just x with y then z which is quite error prone, what could be a conformant way to browse the elements of a struct as if they were members of the same array?

Disclaimer: It obviously only applies to elements of same type, and padding can be detected with a simple static_assert as shown in that other question of mine, so padding, alignment and mixed types are not my problem here.

解决方案

C does not define any way to specify that the compiler must not add padding between the named members of struct Point, but many compilers have an extension that would provide for that. If you use such an extension, or if you're just willing to assume that there will be no padding, then you could use a union with an anonymous inner struct, like so:

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

You can then access the coordinates by their individual names or via the coords array:

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;
}

这篇关于以一致的方式别名结构和数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆