【问题标题】:Does this code violate the strict aliasing rule?这段代码是否违反了严格的别名规则?
【发布时间】:2016-07-13 09:41:12
【问题描述】:

问题:

  1. 下面的代码是否违反了严格的别名规则?也就是说,是否允许智能编译器打印00000(或其他一些讨厌的效果),因为首先作为其他类型访问的缓冲区然后通过int* 访问?

  2. 1234563
  3. 如果不是,移除大括号(所以ptr1ptr2 在同一范围内)会破坏它吗?

  4. 如果是,如何修复代码?

额外问题:如果代码没问题,并且 2. 或 3. 也不要破坏它,如何更改它以破坏严格的别名规则(例如,将大括号循环转换为使用 int16_t)?


int i;
void *buf = calloc(5, sizeof(int)); // buf initialized to 0

{
    char *ptr1 = buf;    
    for(i = 0; i < 5*sizeof(int); ++i)
        ptr1[i] = i;
}

int *ptr2 = buf;
for(i = 0; i < 5; ++i)
    printf("%d", ptr2[i]);

寻找关于此特定代码的确认,如此简短(ish),专家回答,理想情况下使用最少的标准引号,是我所追求的。我没有对严格的别名规则进行长时间的解释,只对与此代码相关的部分进行了解释。如果答案能明确列举上面编号的问题,那就太好了。

还假设一个没有整数陷阱值的通用 CPU,假设int 是 32 位和二进制补码。

【问题讨论】:

    标签: c language-lawyer strict-aliasing


    【解决方案1】:

    不,它没有,但这只是因为内存已分配,并使用字符类型写入。

    内存是使用 malloc 分配的。该对象没有声明1 类型,因为它是用malloc 分配的。因此该对象没有任何有效的类型。

    然后代码使用char 类型访问和修改对象。由于类型为2char 并且没有复制具有有效类型的对象5,因此复制不会将有效类型设置为 char 以进行此访问和后续访问,但将有效类型设置为char,仅在访问期间3。访问后,对象不再具有有效类型。

    然后int 类型用于访问并仅读取该对象。由于对象没有有效类型,因此在读取期间它变为3int。访问后,对象不再具有有效类型。由于int 显然与有效类型int 兼容,因此定义了行为。

    (假设读取的值不是int 的陷阱表示。)


    如果您使用与int 不兼容的非字符类型访问和修改对象,则行为将未定义。

    假设你的例子是(假设sizeof(float)==sizeof(int)):

    int i;
    void *buf = calloc(5, sizeof(float)); // buf initialized to 0
    
    {
        float *ptr1 = buf;    
        for(i = 0; i < 5*sizeof(float); ++i)
            ptr1[i] = (float)i;
    }
    
    int *ptr2 = buf;
    for(i = 0; i < 5; ++i)
        printf("%d", ptr2[i]);
    

    在写入floats 时,对象的有效类型变为float 类型,在写入期间以及对对象的所有后续访问不修改它2 。当int 访问这些对象时,有效类型仍然是float,因为这些值只是被读取而不被修改。之前使用float 的写入将有效类型永久设置为float,直到下一次写入该对象(在这种情况下没有发生)。 intfloat 类型不兼容4,因此行为未定义。


    (以下所有文字均引用自:ISO:IEC 9899:201x)

    1(6.5 表达式 6)
    访问其存储值的对象的有效类型是对象的声明类型(如果有)。 87) 分配的对象没有声明的类型。

    2(6.5 表达式 6)
    如果通过具有类型不是字符类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型,并且不修改存储值的后续访问。

    3(6.5 表达式 6)
    对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

    4(6.5 表达式 8)
    对象的存储值只能由具有以下之一的左值表达式访问 以下类型:88) — 与对象的有效类型兼容的类型, — 与对象的有效类型兼容的类型的限定版本, — 对应于有效类型的有符号或无符号类型 目的, — 一种类型,它是有符号或无符号类型,对应于 对象的有效类型, — 聚合或联合类型,其中包括上述类型之一 成员(递归地包括子聚合或包含联合的成员),或 — 一种字符类型。

    5(6.5 表达式 6)
    如果使用 memcpy 或 memmove 将值复制到没有声明类型的对象中,或者复制为字符类型的数组,则该访问和不修改值的后续访问的修改对象的有效类型 是从中复制值的对象的有效类型(如果有的话)。

    【讨论】:

    • 您隐含地提出了一个我没有想到的有趣观点:由于分配的内存不是通过int 左值分配的,它还没有“变成”int per 6.5 第 6 段当它通过int * 取消引用时,毕竟违反了严格的别名?解析那段是痛苦的。
    • 此时只有一个字符被写入对象,并且没有为对象设置有效类型。读取时变为int,因为对于没有声明类型的对象的所有其他访问,对象的有效类型就是用于访问的左值的类型。
    • 如果在同一点将 int 写入对象,而不是读取,则类型也将变为 int,因为:如果将值存储到没有通过具有非字符类型类型的左值声明类型,则左值的类型成为该访问以及不修改存储值的后续访问的对象的有效类型。
    • @hyde 是的,自动对象已经声明了类型并且不能更改有效类型。由于引用 4.,intchar 不兼容。
    • @AndrewHenle 我认为“复制为字符类型的数组”意味着从另一个对象整体复制一个值,事实并非如此此处(int 值通过字符指针存储,但不复制表示形式)。不幸的是,标准中没有进一步明确定义(许多失败之一)。
    【解决方案2】:

    没有。这不违反严格的别名。

    来自the C Standard6.2.5 类型,第 28 段:

    指向void 的指针应具有相同的表示形式和 对齐要求作为指向字符类型的指针。 48

    注意 48。这指的是脚注 48:

    48) 相同的表示和对齐要求是 意味着可互换性作为函数的参数, 从函数和联合成员返回值。

    所以你可以通过char * 指针访问calloc()'d 内存(假设你的ptrptr1)没有问题。

    虽然这确实是额外的,但由于 7.22.3 内存管理功能,第 1 段指出:

    如果分配成功返回的指针是适当对齐的,所以 可以将它分配给指向任何类型对象的指针 基本对齐要求,然后用于访问这样的 分配空间中的对象或此类对象的数组

    因此,您也可以通过int 指针和char 指针安全地访问calloc()'d 内存。和一个 double 引导指针(假设您在分配的内存范围内)。

    【讨论】:

    • 严格的别名不是对齐要求和表示。
    • @JohannesSchaub-litb 问题所指的与memset() 所做的相同(memset 函数复制了c 的值(转换为unsigned char)到s 指向的对象的每个前n 字符中。)如果您认为这是错误的,请发布您的答案,同时说明memset() 如何将任何类型的内存设置为重复char 值。你是说memset() 违反了严格的别名?
    • @AndrewHenle 没有人说问题中的代码违反了严格的别名规则。但是,您的答案中给出的原因是不正确的。具有相同的对齐和表示并不暗示两种类型可能有别名。
    • @davmac 但是严格别名的根本原因 - 以满足“与对象的有效类型兼容的类型”的要求 - 是因为这种对齐和表示。 如何对象是“兼容的”?
    • @AndrewHenle 如果不是基本的,那么至少严格别名规则的一个重要原因是允许优化。如果没有严格的别名规则,编译器将不得不假设,几乎对任何变量的任何赋值都可能改变指针指向的非易失性值(并且必须从内存中重新获取它),反之亦然,几乎任何赋值通过指针可以改变任何变量的值。严格的别名规则允许编译器假设值不会像这样改变,在它适用的情况下。
    猜你喜欢
    • 1970-01-01
    • 2020-04-11
    • 2016-10-09
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多