繁体   English   中英

严格的别名规则

[英]Strict aliasing rule

我正在阅读有关 reinterpret_cast 的注释及其别名规则( http://en.cppreference.com/w/cpp/language/reinterpret_cast )。

我写了这段代码:

struct A
{
  int t;
};

char *buf = new char[sizeof(A)];

A *ptr = reinterpret_cast<A*>(buf);
ptr->t = 1;

A *ptr2 = reinterpret_cast<A*>(buf);
cout << ptr2->t;

我认为这些规则在这里不适用:

  • T2 是对象的(可能是 cv 限定的)动态类型
  • T2 和 T1 都是(可能是多级的,可能在每一级都有 cv 限定)指向相同类型 T3 的指针(C++11 起)
  • T2 是聚合类型或联合类型,它将上述类型之一保存为元素或非静态成员(递归地包括子聚合的元素和所包含联合的非静态数据成员):这使得转换是安全的从结构的第一个成员和联合的元素到包含它的结构/联合。
  • T2 是对象的动态类型的(可能是 cv 限定的)有符号或无符号变体
  • T2 是对象的动态类型的(可能是 cv 限定的)基类
  • T2 是字符或无符号字符

在我看来,这段代码是不正确的。 我对吗? 代码正确与否?

另一方面,connect函数(man 2 connect)和struct sockaddr呢?

   int connect(int sockfd, const struct sockaddr *addr,
               socklen_t addrlen);

例如。 我们有 struct sockaddr_in 并且我们必须将它转换为 struct sockaddr。 以上规则也不适用,所以这个演员不正确吗?

是啊,这是无效的,但不是因为你转换char*A* :那是因为你没有获得A* ,实际上指向一个A* ,正如你已经确定,没有任何的类型锯齿选项合身。

你需要这样的东西:

#include <new>
#include <iostream>

struct A
{
  int t;
};

char *buf = new char[sizeof(A)];

A* ptr = new (buf) A;
ptr->t = 1;

// Also valid, because points to an actual constructed A!
A *ptr2 = reinterpret_cast<A*>(buf);
std::cout << ptr2->t;

现在类型别名根本不存在(尽管继续阅读,因为还有更多事情要做!)。

实际上,这还不够。 我们还必须考虑对齐 虽然上面的代码可能看起来有效,但为了完全安全,您需要将new放置到正确对齐的存储区域中,而不仅仅是一个随意的char块。

标准库(自 C++11 起)为我们提供了std::aligned_storage来做到这一点:

using Storage = std::aligned_storage<sizeof(A), alignof(A)>::type;
auto* buf = new Storage;

或者,如果您不需要动态分配它,只需:

Storage data;

然后,做你的新安置:

new (buf) A();
// or: new(&data) A();

并使用它:

auto ptr = reinterpret_cast<A*>(buf);
// or: auto ptr = reinterpret_cast<A*>(&data);

它看起来像这样:

#include <iostream>
#include <new>
#include <type_traits>

struct A
{
  int t;
};

int main()
{
    using Storage = std::aligned_storage<sizeof(A), alignof(A)>::type;

    auto* buf = new Storage;
    A* ptr = new(buf) A();

    ptr->t = 1;

    // Also valid, because points to an actual constructed A!
    A* ptr2 = reinterpret_cast<A*>(buf);
    std::cout << ptr2->t;
}

现场演示

即便如此,由于 C++17 这有点复杂; 有关更多信息,请参阅相关的 cppreference 页面并注意std::launder

当然,这整个事情看起来很人为,因为您只需要一个A ,因此不需要数组形式; 实际上,您首先只需创建一个沼泽标准A 但是,假设buf实际上实际上更大,并且您正在创建一个分配器或类似的东西,这是有道理的。

派生 C++ 规则的 C 别名规则包括一个脚注,指定规则的目的是说明事物何时可以别名。 标准的作者认为没有必要禁止实现以不必要的限制方式在事物没有别名的情况下应用规则,因为他们认为编译器作者会尊重谚语“不要阻止程序员做什么需要完成”,标准的作者将其视为 C 精神的一部分。

需要使用聚合成员类型的左值来实际为聚合类型的值设置别名的情况很少见,因此标准不要求编译器识别这种别名是完全合理的。 但是,在不涉及别名的情况下限制性地应用规则会导致类似的情况:

union foo {int x; float y;} foo;
int *p = &foo.x;
*p = 1;

甚至,就此而言,

union foo {int x; float y;} foo;
foo.x = 1;

调用 UB,因为赋值用于使用int访问union foofloat的存储值,这不是允许的类型之一。 任何质量然而,编译器应该能够认识到其上可见新鲜源于一个左值进行操作union foo是一个访问union foo ,和一个访问union foo允许影响的存储值它的成员(如本例中的float成员)。

该标准的作者可能拒绝使脚注规范化,因为这样做需要正式定义何时通过新派生的左值访问是对父级的访问,以及什么样的访问模式构成别名。 虽然大多数情况都非常明确,但也有一些极端情况,用于低级编程的实现可能比用于高端数字运算的实现更悲观,并且标准的作者认为任何能够计算弄清楚如何处理较难的情况应该能够处理简单的情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM