【问题标题】:Strict aliasing within an expression表达式中的严格别名
【发布时间】:2021-06-06 12:33:25
【问题描述】:

假设我们有以下代码:

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a[] = { 1, 2, 3, 4 };
    const size_t n = sizeof(a) / sizeof(uint16_t);
    
    for (size_t i = 0; i < n; i++) {
        uint16_t *b = (uint16_t *) ((uint8_t *) a + i * sizeof(uint16_t));
        printf("%u\n", *b);
    }
    
    return 0;
}

显然,将 a 转换为 uint8_t 指针并不违反,所以这个问题是关于将结果指针转换为 uint16_t 指针。据我了解,根据标准,它确实违反了严格的别名规则。但是,从实际的角度来看,我不确定,因为ab 的类型是兼容的。唯一的潜在违规是 b 为仅存在于这一个表达式中的 uint8_t 指针起别名。所以在我的理解中,即使它违反了规则,我也会怀疑它是否会导致未定义的行为。可以吗?

请注意,我并不是说这段代码有意义。该问题仅用于有关理解严格别名的教育目的。

【问题讨论】:

    标签: c language-lawyer strict-aliasing


    【解决方案1】:

    不是严格的别名违规。

    auint8_t 的转换以及随后的指针运算是安全的,因为转换为字符指针类型时出现异常。

    C standard 第 6.3.2.3p7 节指出:

    指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐 68) 引用的类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。 当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续递增,直到对象的大小,产生指向对象剩余字节的指针。

    转换回和随后的取消引用是安全的,因为b 指向类型为uint16_t 的对象(特别是数组a 的成员),匹配b 的指向类型。

    第 6.5p7 节规定:

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

    • 与对象的有效类型兼容的类型,
    • 与对象的有效类型兼容的类型的限定版本,
    • 对应于对象有效类型的有符号或无符号类型,
    • 一种有符号或无符号类型,对应于对象有效类型的限定版本,
    • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括 子聚合或包含联合的成员),或
    • 一种字符类型。

    【讨论】:

    • 不需要“指针到字符类型”转换 - uint32_t a[]; b = (uint32_t*)((uint16_t*)a + i * sizeof(uint32_t)/sizeof(uint16_t)) 也可以,对吧?问题是,生成的 uint16_t 指针值与 uint16_t 正确对齐。
    • @KamilCuk 这不是绝对安全的,因为您没有一个 uint16_t 类型的数组,您可以对其进行算术运算。 6.3.2.3p7 特别允许对字符类型进行指针运算以访问对象的表示。
    • @dbush 如果在这两种情况下uint32_tasizeof(uint32_t) 对应,但b 保持uint16_t 不变,那会怎样?也许这更符合我在表达式中使用别名的意图。
    • @Pedro 这将在取消引用时违反别名,因为您将访问uint32_t,就好像它是一个`uint16_t。
    • @dbush 我想我可以回答我自己的问题:b 将不再与a 兼容,因此这将违反严格的别名规则。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-15
    • 1970-01-01
    • 2018-03-02
    • 1970-01-01
    • 2019-01-29
    • 2014-02-08
    • 1970-01-01
    相关资源
    最近更新 更多