当前位置: 首页 > 知识库问答 >
问题:

严格的别名规则

国景铄
2023-03-14

我正在阅读关于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都是指向相同类型T3的指针(可能是多级的,可能在每个级别都是cv限定的)(因为C11)
  • T2是一个聚合类型或并集类型,它将上述类型之一作为元素或非静态成员(递归地包括子集合的元素和所包含的并集的非静态数据成员):这使得从结构的第一个成员以及从并集的元素到包含它的结构/并集的转换是安全的
  • T2是对象的动态类型的(可能是cv限定的)有符号或无符号变体
  • T2是对象的动态类型的基类(可能是cv限定的)
  • T2是字符或无符号字符

在我看来,这个代码不正确。我说得对吗?代码正确与否?

另一方面,连接功能(人2连接)和结构索卡德怎么样?

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

例如,我们有structsockaddr_in,我们必须将其转换为structsocketaddr。上面的规则也不适用,所以这个演员阵容不正确吗?

共有2个答案

爱琪
2023-03-14

派生出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 的并集 foo浮点数的存储值,这不是允许的类型之一。但是,任何高质量的编译器都应该能够认识到,对从联合 foo 新派生的左值执行的操作是对联合 foo 的访问,并且允许访问联合 foo 影响其成员的存储值(在本例中为 float 成员)。

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

暨高洁
2023-03-14

是的,这是无效的,但不是因为您将< code>char*转换为< code>A*:这是因为您没有获得实际指向< code>A*的< code>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;

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

  • (使用-Wstrict别名=2进行现场演示)

实际上,这还不够。我们还必须考虑结盟。虽然上面的代码看起来可以正常工作,但为了完全安全,您需要将代码放置在正确对齐的存储区域中,而不仅仅是一个随意的chars块。

标准库(从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开始,这就有些复杂了;有关更多信息,请参阅相关的cp首选项页面,并注意std::launder

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

 类似资料:
  • 基本上,当启用严格别名时,此代码是否合法? 这里,我们通过另一种类型的指针(指向<code>void*</code>的指针)访问一种类型(<code<int*</code>)的对象,因此我认为这确实是一种严格的别名冲突。 但是,试图突出未定义行为的样本使我怀疑(即使它不能证明它是合法的)。 首先,如果我们将<code>int*</code>和<code>char*</code>别名,我们可以根据优

  • 我的理解是,由于所谓的“严格混淆现象规则”,以下代码在C中具有未定义的行为。 特别是,C编译器可能会完全忽略赋值,因为它可以假定

  • 示例代码: 我认为这是常见的,被认为是可以接受的。该标准确实保证结构中没有初始填充。但是,这种情况未在严格别名规则中列出(C 17 [basic.lval]/11): 如果程序尝试通过下列类型之一以外的 gl 值访问对象的存储值,则行为未定义: (11.1)物体的动态类型, (11.2) 对象的动态类型的 cv 限定版本, (11.3) 与对象的动态类型相似的类型(如 7.5 中所定义), (11

  • 我试图理解C和C的严格别名规则。我已经问了很多关于这个问题的问题,并做了一些阅读,但我只想澄清一些事情。 指向任何类型的指针都可以别名void*,这就是为什么我们可以这样做: 但是: (问题1)任何指针类型都可以别名char指针吗? 问题2:此外,当将任何指针类型别名为char或void指针类型时,我们需要确保正确的对齐方式,对吗?在堆栈上不能保证char或char数组在我们从新或malloc获得

  • 我无法解释此程序的执行行为: 因此,当使用-O3和gcc编译它,并使用参数25运行时,它会引发一个segfault。如果没有优化,它可以正常工作。我已经对它进行了反汇编:它正在进行矢量化,编译器假设数组以16字节对齐,因此它使用。显然是UB,尽管我无法解释。我知道严格别名规则,但情况并非如此(我希望如此),因为据我所知,严格别名规则不适用于s。为什么gcc假设这个指针是对齐的?即使进行了优化,Cl

  • 当违反严格的混淆现象规则时,我试图掌握未定义的行为。为了理解它,我读了很多关于SO的文章。然而,还有一个问题:我并不真正理解两种类型的非法别名。cpp-参考指出: 类型混淆现象 每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非以下之一为真,否则行为未定义: AliasedType和DynamicType类似 AliasedType是Dy