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

时间:2015-02-10 14:40:37

标签: c gcc standards c99 strict-aliasing

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

  

对象的存储值只能由左值访问   具有以下类型之一的表达式73)或88):

     
      
  • (其他与问题无关的陈述)

  •   
  • 包含上述内容之一的聚合或联合类型   其成员之间的类型(包括,递归地,成员   subaggregate或contains union)

  •   

请考虑以下事项:

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

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

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

另一种方式呢?即:

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

在这两种情况下,最终,我们在这里处理的所有内容都是float,所以也许可以吗?我只是不确定。

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

3 个答案:

答案 0 :(得分:3)

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

  

在结构对象中,非位字段成员和单位   哪些位字段驻留的地址按顺序增加   他们被宣布。 适当地指向结构对象的指针   已转换,指向其初始成员(或者如果该成员是   位字段,然后到它所在的单元,反之亦然。   结构对象中可能有未命名的填充,但不在其中   开始。

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

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

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

Aliasing Arrays through structs

结论是,案例#2是访问成员real的好方法。 此外,如果结构没有内部填充(取决于平台),您甚至可以通过imag访问数组的第二个成员。

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

答案 1 :(得分:1)

我尝试使用这种投射方法重现一些问题。 一个问题是当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;
}

可能会有更多问题,但在我看来这件事有风险。

答案 2 :(得分:0)

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

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

/* 1 */
carray[n].real

/* 2 */
asfloats[n * 2]

标准说上面的两种形式都是允许访问float对象的方法,但它没有说明它们是否都引用相同的对象,或后者是否引用任何对象。他们可能都被允许,可能甚至意味着相同的东西,但这取决于struct MyComplex的实现定义表示(除了{{1}时}为零,这两个总是被允许和等价的。)

例如,如果n具有四字节表示,并且编译器恰好填充float表示为16字节的倍数,那么类型(2)的一些访问(旨在<{1}}成员通过struct)将无效。

访问real成员会更糟糕:如果编译器选择在8字节边界上对齐每个成员,那么也可以使用它,因此需要在float *之间填充和imag

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