【问题标题】:Strict aliasing and references to compile-time C arrays对编译时 C 数组的严格别名和引用
【发布时间】:2020-02-13 05:55:34
【问题描述】:

给定以下代码

#include <cassert>
#include <climits>
#include <cstdint>
#include <iostream>

static_assert(CHAR_BIT == 8, "A byte does not consist of 8 bits");

void func1(const int32_t& i)
{
    const unsigned char* j = reinterpret_cast<const unsigned char*>(&i);
    for(int k = 0; k < 4; ++k)
        std::cout << static_cast<int>(j[k]) << ' ';
    std::cout << '\n';
}

void func2(const int32_t& i)
{
    const unsigned char (&j)[4] = reinterpret_cast<const unsigned char (&)[4]>(i);
    for(int k = 0; k < 4; ++k)
        std::cout << static_cast<int>(j[k]) << ' ';
    std::cout << '\n';
}

int main() {
    func1(-1);
    func2(-1);
}

从语言规则可以看出func1 是可以的,因为指向unsigned char 的指针可以为任何其他类型设置别名。我的问题是:这是否扩展到对已知长度的 C 数组的 C++ 引用?直觉上我会说是的。 func2 定义明确还是会触发未定义的行为?

我已经尝试使用 Clang 和 GCC 以及 -Wextra -Wall -Wpedantic 和 UBSAN 的所有可能组合来编译上述代码,并且没有收到任何警告并且始终得到相同的输出。这显然没有说明没有 UB,但我无法触发任何通常的严格别名类型优化错误。

【问题讨论】:

  • 我的猜测(我不敢将此作为答案发布)是没有 UB假设int32_t 的定义已验证。也就是说,只要这是一个 4 字节(即 4 个字符)连续的内存块,就没有问题。
  • 这就是我使用int32_t 而不仅仅是int 的原因(因为这可能会引发有关sizeof(int) != 4 的情况的问题,例如在DOS 等上)
  • @Adrian 7.20.1.1 Exact-width integer types from the C standard 是相关的:“typedef 名称 intN_t 指定宽度为 N、无填充位和二进制补码表示的有符号整数类型。”如果int32_t 存在,它似乎必须是连续的。不过,在这种情况下,“字节”仍然可以是 8、16 或 32 位。
  • @JonasMüller 但 sizeof(int32_t) 也不一定是 4,因为 char 不一定是 8 位。此外,sizeof(int32_t) 不像 4 那样是一个幻数,所以最好使用它。
  • @JonasMüller:“从语言规则来看,func1 很好”。事实上,不完全是,j[0] 可以,j + 1 可以。但是j[1]j[2] 是迂腐的UB,因为j 没有指向array(与func2 相反:-))。

标签: c++ language-lawyer undefined-behavior strict-aliasing


【解决方案1】:

这是未定义的行为。

关于reinterpret_cast的含义,这里有[expr.reinterpret.cast]

11 T1 类型的泛左值表达式可以强制转换为类型 “reference to T2”,如果类型为“pointer to T1”的表达式可以是 使用 a 显式转换为“指向 T2 的指针”类型 reinterpret_cast。结果引用与源相同的对象 glvalue,但具有指定的类型。 [ 注意:也就是说,对于左值,a 参考转换 reinterpret_cast(x) 与 使用内置的 & 和 * 转换 *reinterpret_cast(&x) 运算符(同样适用于 reinterpret_cast(x))。 —— 尾注 ] 没有临时创建,没有复制,构造函数或 不调用转换函数。

这告诉我们,只要 reinterpret_cast&lt;const unsigned char (*)[4]&gt;(&amp;i) 有效,强制转换 int func2 就有效。这里没有震惊。但问题的关键在于,您可能无法从指针转换中得到任何有意义的东西。关于这个问题,我们在 [basic.compound] 上进行了讨论:

4 两个对象 a 和 b 是指针可互转换的,如果:

  • 它们是同一个对象,或者
  • 一个是标准布局联合对象,另一个是该对象的非静态数据成员 ([class.union]),或者
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有 非静态数据成员,该对象的第一个基类子对象 ([class.mem]),或
  • 存在一个对象 c,使得 a 和 c 可以指针互转换,而 c 和 b 可以指针互转换。

如果两个对象是指针可互转换的,那么它们具有相同的 地址,并且可以从指针中获得指向一个的指针 通过reinterpret_­cast 发送给另一个。 [ 注意:数组对象及其 第一个元素不是指针可相互转换的,即使它们有 同一个地址。 — 尾注 ]

这是一个详尽的有意义的指针转换列表。因此,我们不允许获取这样的数组地址,因此它不是有效的数组 glvalue。因此,您对转换结果的进一步使用是未定义的。

【讨论】:

  • “因此,对强制转换结果的任何进一步使用都是未定义的。” 没有。您可以将 address-of 运算符应用于强制转换的结果或强制转换回原始类型。所以至少还有 2 种用途。
猜你喜欢
  • 1970-01-01
  • 2010-10-18
  • 2013-12-14
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 2012-11-08
  • 2012-12-19
  • 2019-01-29
相关资源
最近更新 更多