【问题标题】:Strict aliasing violation and memory address严格的别名违规和内存地址
【发布时间】:2021-10-21 18:32:42
【问题描述】:

我正在尝试了解 C 和 C++ 的严格别名规则。我已经对此提出了很多问题并阅读了一些内容,但我只是想澄清一些事情。

// void* can alias any other type:
int anInt;
void* pToVoid = (void*)&anInt; // Allowed
// So can char*
char* pToChar = (char*)&anInt; // Allowed

指向任何类型的指针都可以别名为 void*,这就是为什么我们可以这样做:

int* myNewInt = (int*)malloc(sizeof(int));

但是:

(问题1)任何指针类型都可以别名char指针吗?

    char myChars[4];
    int* pInt = (int*)myChars; // Is this allowed?
// I'm guessing so because this is how we create buffers
    float* pFloat = (float*) pInt; // I know this is strict aliasing violation

问题 2:当将任何指针类型别名为 char 或 void 指针类型时,我们需要确保正确对齐,对吗?当我们从 new 或 malloc 获取 char 或 char 数组时,堆栈上不能保证对齐,对吧?

我的第三个问题是,当你强制转换指针或指针对同一内存进行别名时,是否违反了严格的别名规则?例如:

struct MyStruct
{
    int myInt;
    float myFloat;
};

int main()
{
    MyStruct myStructObj;
    float* pFloat = &myStructObj.myInt; // This is aliasing the wrong type, not allowed
// However if I move the float* then it no longer aliases the wrong type
    pFloat += 1;
// Now the pointer points to the right type. However is it now too late? My program
// has UB because I first aliased the pointer in the first place?
// On the other hand I assume this is allowed though:
   float pFloat = (float*)(((char*)&myStructObj.myInt) + sizeof(int));
// This way the float pointer never aliases the int, the int pointer is 
// first cast to char*, then char* to float*, which I assume is allowed.
}

换句话说,关于访问相同内存或分配不同指针类型的严格别名规则是什么?因为如果它只是关于内存访问,那么我将 float* 分配给 int* 的示例很好,因为我先移动它,对吧?

编辑:有人指出 C 和 C++ 的别名规则不同,因此我将其标记为关于 C++。

【问题讨论】:

  • 也许这有帮助:stackoverflow.com/questions/98650/…。但是根据我的经验......用指针做这种事情最终会咬你。
  • 你问的是c还是c++?它们是不同的语言,别名规则不同,请只标记其中一种
  • 转换本身不是问题,解引用和算术都是。
  • en.cppreference.com/w/cpp/language/reinterpret_cast 阅读(或重新阅读)此页面,如果您仍然需要它,请要求澄清 - 您基本上需要将此页面作为答案发布。
  • @RichardCritten 我读过,我仍然没有得到详细的信息。根据上面的评论“转换本身不是问题,取消引用和算术是”。我不明白为什么 int a;浮动 b;浮动* pToFloat = (float*)&a; ((char*)pToFloat) + 4;是非法的。我已经将浮点指针转换为 char*,这对别名很好,我对 char* 进行算术运算,而不是 float*。

标签: c++ strict-aliasing


【解决方案1】:

问题:
在 C++ 中,别名是关于寄存器使用的。浮点值保存在不同于用于整数的寄存器中的情况并不少见。实际上,当编译器缓存一个值时,它必须将它绑定到一个寄存器。如果您将相同的值加载为不同的类型,最终可能会有多个寄存器绑定到相同的值(例如,一个具有 int 值的寄存器和一个具有浮点版本的寄存器)。
解决此问题的一种方法是告诉编译器始终写入内存(禁用某些寄存器缓存 - 使代码变慢)
另一个是向编译器保证它可以缓存任何它认为合适的方式,并且你永远不会改变类型。

实用程序
现代解决方案是 (spans) 在旧编译器gsl::span 上创建了一个库来支持此解决方案。两种解决方案都支持“as_bytes”函数。

安全处理此类问题的现代方法是将内存建模为 std::bytes(旧代码中的 gsl::bytes)。正如这个答案所说的Has a std::byte pointer the same aliasing implications as char*?standard 指定 std::byte 永远不会出现别名问题。 此解决方案确实会强制您显式复制,这可能是设计使然。

更新:
您的问题:

  1. 是的,任何指向 char 的指针都可以避免别名问题(但更喜欢字节)
  2. 如果您将对象创建为它们自己的类型并生成字节跨度,那么对齐就不会成为问题。字节没有对齐。
  3. 每当您将指向同一内存的指针作为不同类型引用时,就会违反别名。如上所述,这可能会创建两个缓存值,然后可能会变得不同步。

更新: 刚刚发现https://en.cppreference.com/w/cpp/numeric/bit_cast。酷

附加
对不起,我刚刚意识到我错过了你的问题。 保证将任意指向 char 的指针转换为基本类型(如 int)。不过,这与别名无关。这与CPU有关。它将在 x86/x64 上工作,因为 CPU 支持这一点。并非所有 CPU 都可以。但是,从字符/字节指针复制总是有效的。

【讨论】:

  • 这并不能以任何方式回答问题
  • 这也是非常错误的,我不知道从哪里开始。作为一个随机示例,此答案假定有两类寄存器(整数和 FP),但任何两种类型都可以在 C++ 中别名,例如std::ofstreamstd::map<std::string, enum>。这些类型甚至不适合 CPU 寄存器。
  • 好吧,如果我错了,cppreference 也是 - 严格别名和相关规则的目的是启用基于类型的别名分析,如果程序可以有效地创建两个指针到不相关的类型(例如,int* 和 float*)可以同时存在,并且两者都可以用于加载或存储相同的内存(请参阅 SG12 反射器上的此电子邮件)。
  • @Tiger4Hire:如果编译器必须允许在同一上下文中使用的左值可能会产生别名,即使它们是 明显相关的是完全不相交的。然而,在没有-fno-strict-aliasing 的情况下,clang 和 gcc 无法进行有意义的处理的程序中只有一小部分符合该描述。此外,clang 和 gcc 的行为可以被描述为正确且没有错误的唯一方法是,如果有人以一些非常奇怪的方式扭曲了标准的术语......
  • ...这会严重降低语言的实用性。例如,在这两个编译器中,如果一个存储区域保存位模式 x 的位 a T1,然后用具有其他模式 y 的 T2 写入,这会将存储的类型更改为 T2,但如果代码然后写入一个 T2,两个 T2 类型的写入加在一起可能不会被视为修改了存储,因此可以将其视为 T1 类型。因此,唯一安全的方法是避免存储位模式可能与之前用不同类型写入的对象相匹配的任何对象。
【解决方案2】:

严格别名违规是关于通过不兼容的句柄访问数据。在您的代码中,您永远不会访问数据。 转换指针只是转换指针的值,它与别名违规无关。

void* 可以为任何其他类型起别名:

是的,您可以将任何其他指针转换void *类型的指针。

任何指针类型都可以为char指针取别名吗?

指针必须指向与其指向的类型对齐的内存位置。假设C11 6.3.2.3p7C++draft expr#static.cast-13C++draft expr#reinterpret.cast-7。当指针未对齐时,我看到结果在 C 中未定义,在 C++ 中未指定。

float* pFloat = (float*) pInt; // I know this is strict aliasing violation

不,不是,你没有访问指针后面的数据。 假设指针正确对齐(可能不是)和sizeof(float) == sizeof(myChars)(可能不是):现在,如果你会这样做,例如*pFloat = 1.0;,那么你实际上会访问数据,然后你可能最终与别名违规有关。我认为Is using the result of new char[] or malloc to casted float * is UB (strict aliasing violation)? 很好地回答了所有情况。

当我们从 new 或 malloc 获取 char 或 char 数组时,堆栈上不能保证对齐,对吧?

是的。

当你强制转换一个指针或一个指针对同一内存进行别名时,是否违反了严格的别名规则?

不,转换指针不会访问数据。不,指向同一位置的两个指针不会访问指针后面的数据。

访问相同内存或分配不同指针类型是否有严格的别名规则?

仅关于访问。

因为如果它只是关于内存访问,那么我将 float* 分配给 int* 的示例很好,因为我先移动它,对吧?

因此,有不同的规则会影响它。不能保证MyStruct::myFloat 从一开始就位于sizeof(int) - 编译器可以在结构成员之间插入填充。使用offsetof 宏。

【讨论】:

  • 指针转换可以访问 C++ 中的数据,我想。我必须仔细检查在哪些继承情况下会发生这种情况,但是 IIRC 存在多重继承情况,编译器需要查看 vtable(或类似的东西)以在运行时找到基本偏移量。跨度>
  • @MSlaters :请参阅我在劣质(显然)答案en.cppreference.com/w/cpp/language/… 中引用的页面上的(注释部分)。你是对的,并不是所有的指针都是可以相互转换的
  • 也就是说,没有任何情况下需要访问 v-table(这毫无意义)。您所描述的与多重继承有关 - 和向下转换,与类型别名无关。见这里stackoverflow.com/questions/48997211/…
猜你喜欢
  • 2017-02-25
  • 1970-01-01
  • 2020-04-11
  • 2015-01-16
  • 1970-01-01
  • 2014-06-28
  • 2012-12-19
相关资源
最近更新 更多