gpt4 book ai didi

c++ - C+ +'s Strict Aliasing Rule - Is the ' char' 别名豁免一条 2 路街道?

转载 作者:IT老高 更新时间:2023-10-28 22:30:20 27 4
gpt4 key购买 nike

就在几周前,我了解到 C++ 标准有一个严格的别名规则。基本上,我问了一个关于移位的问题——而不是一次移位一个字节,为了最大限度地提高性能,我想用(分别为 32 或 64 位)加载处理器的 native 寄存器并执行 4/8 的移位字节全部在一条指令中。

这是我想避免的代码:

unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };

for (int i = 0; i < 3; ++i)
{
buffer[i] <<= 4;
buffer[i] |= (buffer[i + 1] >> 4);
}
buffer[3] <<= 4;

相反,我想使用类似的东西:

unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
unsigned int *p = (unsigned int*)buffer; // unsigned int is 32 bit on my platform
*p <<= 4;

有人在评论中指出我提出的解决方案违反了 C++ 别名规则(因为 p 的类型为 int* 而缓冲区的类型为 char* 而我是取消引用 p 以执行移位。(请忽略可能的对齐和字节顺序问题——我处理了这个片段之外的问题)我很惊讶地了解到他的严格别名规则,因为我经常对来自缓冲区的数据进行操作,将其从一种类型到另一种类型,从来没有任何问题。进一步的调查显示,我使用的编译器(MSVC)没有强制执行严格的别名规则,因为我只是在业余时间开发 gcc/g++ 作为爱好,我可能只是没有还没有遇到这个问题。

然后我问了一个关于严格别名规则和 C++ 的 Placement new 运算符的问题:

IsoCpp.org 提供了关于放置新的常见问题解答,并提供了以下代码示例:

#include <new>        // Must #include this to use "placement new"
#include "Fred.h" // Declaration of class Fred
void someCode()
{
char memory[sizeof(Fred)]; // Line #1
void* place = memory; // Line #2
Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
// The pointers f and place will be equal
// ...
}

这个例子很简单,但我问自己,“如果有人在 f 上调用一个方法——例如 f->talk() 怎么办?那时我们将取消引用 f,它指向与 memory 相同的内存位置(类型为 char*。我读过很多地方char* 类型的变量可以豁免任何类型的别名,但我的印象是它不是“双向街道”——意思是 char* 可以别名(读/写)任何类型 T,但类型 T 只能用于别名 char* if T 本身属于 char*。当我输入这个时,这对我来说没有任何意义,所以我倾向于相信我的声明是初始(位移示例)违反严格别名规则为假。

有人可以解释一下什么是正确的吗?我一直在努力理解什么是合法的,什么是不合法的(尽管阅读了许多关于该主题的网站和 SO 帖子)

谢谢

最佳答案

别名规则意味着该语言仅在以下情况下 promise 您的指针取消引用是有效的(即不会触发未定义的行为):

  • 您通过兼容类的指针访问对象:无论是它的实际类还是它的一个父类(super class),正确转换。这意味着如果 B 是 D 的父类(super class)并且你有 D* d指向有效的 D,访问 static_cast<B*>(d) 返回的指针可以,但访问 reinterpret_cast<B*>(d) 返回的是不是。后者可能未能说明 D 中 B 子对象的布局。
  • 您可以通过指向 char 的指针访问它.由于 char 是字节大小和字节对齐的,因此您无法从 char* 读取数据。同时能够从 D* 读取它.

也就是说,标准中的其他规则(特别是关于数组布局和POD类型的规则)可以被解读为确保您可以使用指针和reinterpret_cast<T*>在 POD 类型和 char 之间为 双向 起别名数组,如果你确保有一个适当大小的字符数组和对齐

换句话说,这是合法的:

int* ia = new int[3];
char* pc = reinterpret_cast<char*>(ia);
// Possibly in some other function
int* pi = reinterpret_cast<int*>(pc);

虽然这可能会调用未定义的行为:

char* some_buffer; size_t offset; // Possibly passed in as an argument
int* pi = reinterpret_cast<int*>(some_buffer + offset);
pi[2] = -5;

即使我们可以确保缓冲区足够大,可以容纳三个 int s,对齐可能不正确。与所有未定义行为的实例一样,编译器绝对可以做任何事情。三种常见的情况可能是:

  • 代码可能 Just Work (TM),因为在您的平台中,所有内存分配的默认对齐方式与 int 相同。
  • 指针转换可能会将地址四舍五入到 int 的对齐方式(类似于 pi = pc & -4),可能会使您读/写到错误内存。
  • 指针取消引用本身可能会以某种方式失败:CPU 可能会拒绝未对齐的访问,从而导致您的应用程序崩溃。

既然你总是想像魔鬼一样抵御 UB,你需要一个 char具有正确大小和对齐方式的数组。最简单的方法是从一个“正确”类型(在本例中为 int)的数组开始,然后通过 char 指针填充它,这是允许的,因为 int 是 POD 类型。

附录: 使用放置后 new ,您将能够调用对象上的任何函数。如果构造正确并且由于上述原因没有调用 UB,那么您已经在所需位置成功创建了一个对象,因此任何调用都可以,即使该对象是非 POD(例如,因为它具有虚函数)。毕竟,任何分配器类will likely use placement new 在他们获得的存储中创建对象。请注意,仅当您使用展示位置 new 时才必须如此。 ;类型双关语的其他用法(例如,使用 fread/fwrite 进行天真的序列化)可能会导致对象不完整或不正确,因为需要对对象中的某些值进行特殊处理以维护类不变量。

关于c++ - C+ +'s Strict Aliasing Rule - Is the ' char' 别名豁免一条 2 路街道?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37259909/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com