与聚合或联合类型相关的严格别名

Strict aliasing in relation to aggregate or union types

我试图理解 C99 标准中以下语句的含义 (C99; ISO/IEC 9899:1999 6.5/7)

An object shall have its stored value accessed only by an lvalue expression that has one of the following types 73) or 88):

  • (other statements unrelated to question omitted)

  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union)

考虑以下几点:

typedef struct MyComplex {
    float real;
    float imag;
} MyComplex;

MyComplex *carray = malloc(sizeof(*carray) * 10);
float *as_floats = (float *)carray;

这是否合法,因为 MyComplex 结构包含 as_floats 指针的兼容 float 类型?

反过来呢?即:

float *farray = malloc(sizeof(*farray) * 10);
MyComplex *as_complex = (MyComplex *)farray;

在这两种情况下,最终,我们在这里处理的一切都是 float,所以也许没问题?我只是不确定。

我问,因为我正在处理一个遗留代码库,它到处都在做这种事情,到目前为止,一切似乎都很好。但我觉得我们在这里是在玩火。试图弄清楚我是否需​​要在编译器命令行上禁用严格别名。

(我相信)所有版本的标准纵容案例 #1 的 6.7.2.1 中的注释 13 明确。您很少会得到更明确的答案! 我的重点

Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.

http://port70.net/~nsz/c/c99/n1256.html#6.7.2.1

是的!您可以强制转换结构以直接访问其第一个成员! 为什么有人认为这比 &(carray->real) 更好尚不清楚。但这绝对是合法的。

正如另一位评论者所指出的,我在上一个问题中讨论了案例 #2。

结论是案例 #2 是访问成员 real 的一种 OK 方式。 此外,如果该结构没有内部填充(取决于平台),您甚至可以通过 imag.

访问数组的第二个成员

我说依赖于平台,但其他调查没有提供任何已知平台,这样的结构包含填充。

我尝试重现使用这种转换方法时出现的一些问题。 一个问题是当 struct 使用位字段时,另一个问题是 - 当存在强制 struct 成员对齐到某个大小的编译器指令时:

// issue 1 : problem with bitFields
typedef struct MyComplex_1 {
    unsigned int real : 1;
    unsigned int imag : 2;
} MyComplex_1;

// issue 2 : problem with members forced alignment
typedef struct MyComplex_2 {
    unsigned int real;
    unsigned int imag;
} __attribute__ ((aligned (64))) MyComplex_2; // GCC compiler directive

int main()
{
    MyComplex_1 arr_1[] = {{1, 2}, {1, 2}};
    MyComplex_2 arr_2[] = {{1, 2}, {1, 2}};

    int i;

    // for bitfield issue
    for (i=0; i<2; i++)
        printf("struct (%d,%d)\n", arr_1[i].real, arr_1[i].imag);

    for (i=0; i<4; i++)
        printf("var (%d)\n", ((unsigned int*)arr_1)[i]);

    // for struct member forced alignement issue
    for (i=0; i<2; i++)
        printf("struct (%d,%d)\n", arr_2[i].real, arr_2[i].imag);

    for (i=0; i<4; i++)
        printf("var (%d)\n", ((unsigned int*)arr_2)[i]);

    return 0;
}

可能还有更多的问题,但是在我看来这个东西是有风险的

我同意你在玩火,但不是因为你引用的标准部分。

任何对象,包括作为聚合类型(数组或结构)对象的组件的对象,都以地址和类型为特征。根据给定的声明,考虑这些形式的访问(对于 0 <= n < 10):

/* 1 */
carray[n].real

/* 2 */
asfloats[n * 2]

标准说以上两种形式都允许访问float对象,但没有说明它们是否都引用相同的对象,或者后者是否引用了任何对象。他们 may 都被允许并且 may 甚至意味着同样的事情,但这取决于 struct MyComplex 的实现定义的表示(除了当 n 为零时,两者总是允许的并且是等价的)。

例如,如果 float 具有四字节表示,并且编译器恰好将 struct 表示填充为 16 字节的倍数,则某些类型 (2) 的访问(打算通过 float * 发送给 real 成员)将无效。

访问 imag 成员时更糟糕:如果编译器选择将每个成员对齐到 8 字节边界,您也可能会遇到麻烦,因此需要在 realimag.

另请注意,填充甚至不一定是实现的固定特征。许多编译器提供影响其填充决策的选项,因此您正在使用的代码是否具有已定义的行为可能取决于您的编译器选项。