c++11 严格别名规则是否允许通过 char *、char(&)[N]、甚至 std::array<char, N>& 和 -fstrict-aliasing -Wstrict-aliasing=2 访问 uint64_t?

san*_*orn 5 c++ strict-aliasing language-lawyer c++11

根据有关C++11/14严格别名规则的stackoverflow答案:

如果程序尝试通过以下类型之一以外的泛左值访问对象的存储值,则行为未定义:

  • 对象的动态类型,

  • 对象动态类型的 cv 限定版本,

  • 与对象的动态类型类似的类型(如 4.4 中定义),
  • 与对象的动态类型相对应的有符号或无符号类型,
  • 与对象动态类型的 cv 限定版本相对应的有符号或无符号类型,
  • 聚合或联合类型,其元素或非静态数据成员中包括上述类型之一(递归地包括子聚合或包含的联合的元素或非静态数据成员),
  • 是对象动态类型的(可能是 cv 限定的)基类类型的类型,
  • acharunsigned char类型。

我们可以使用以下方式访问其他类型的存储吗

(1)char *

(2)char(&)[N]

(3)std::array<char, N> &

不依赖于未定义的行为

constexpr uint64_t lil_endian = 0x65'6e'64'69'61'6e; 
    // a.k.a. Clockwise-Rotated Endian which allocates like
    // char[8] = { n,a,i,d,n,e,\0,\0 }

const auto& arr =   // std::array<char,8> &
    reinterpret_cast<const std::array<char,8> &> (lil_endian);

const auto& carr =  // char(&)[8]>
    reinterpret_cast<const char(&)[8]>           (lil_endian);

const auto* p =     // char *
    reinterpret_cast<const char *>(std::addressof(lil_endian));

int main()
{
    const auto str1  = std::string(arr.crbegin()+2, arr.crend() );

    const auto str2  = std::string(std::crbegin(carr)+2, std::crend(carr) );

    const auto sv3r  = std::string_view(p, 8);
    const auto str3  = std::string(sv3r.crbegin()+2, sv3r.crend() );

    auto lam = [](const auto& str) {
        std::cout << str << '\n'
                  << str.size() << '\n' << '\n' << std::hex;
        for (const auto ch : str) {
            std::cout << ch << " : " << static_cast<uint32_t>(ch) << '\n';
        }
        std::cout << '\n' << '\n' << std::dec;
    };

    lam(str1);
    lam(str2);
    lam(str3);
}
Run Code Online (Sandbox Code Playgroud)

所有 lambda 调用都会产生:

endian
6

e : 65
n : 6e
d : 64
i : 69
a : 61
n : 6e
Run Code Online (Sandbox Code Playgroud)

godbolt.org/g/cdDTAM(启用 -fstrict-aliasing -Wstrict-aliasing=2 )

wandbox.org/permlink/pGvPCzNJURGfEki7

Oli*_*liv 2

严格的别名规则实际上非常简单:如果一个对象不是另一个对象的子对象,则具有重叠生命周期的两个对象不能具有重叠的存储区域。(*)

然而,允许读取对象的内存表示。对象的内存表示是[basic.types]/4的序列unsigned char

T 类型对象的对象表示unsigned char是T 类型对象所占用的N 个对象的序列,其中 N 等于sizeof(T)。对象的值表示是保存类型 T 的值的一组位。

因此在你的例子中:

  • lam(str1)是UB(未定义行为);
  • lam(str2)是 UB (数组及其第一个元素不能进行指针互换);
  • lam(str3)标准中没有表述为 UB ,如果您替换charunsigned charUB ,可能会认为您正在阅读对象表示。(它也没有定义,但它应该适用于所有编译器)

p因此,使用第三种情况并更改to的声明const unsigned char*应该始终会产生预期的结果。对于其他两种情况,它可以使用这个简单的示例,但如果代码更复杂或使用较新的编译器版本,则可能会中断。


(*) 此规则有两个例外:一个是具有共同初始化序列的联合体成员;一个用于数组unsigned char或为其他对象std::byte提供存储。

  • @santhorn不幸的是,c++ 标准法律集中似乎存在一个漏洞。因此,您应该应用*判例*:编译器接受的常见做法。我相信这两个答案和你的第三个案例是常见的做法。 (2认同)