首页 > 解决方案 > 铸造一个空指针来检查内存对齐

问题描述

几个帖子中经常出现的解决方案之一是关于如何确定void *指向对齐内存的点是否涉及强制转换 void 指针。也就是说,我得到一个void *ptr = CreateMemory(),现在我想检查指向的内存是否ptr是某个值的倍数,比如 16。

请参阅如何确定内存是否对齐?这提出了一个具体的要求。

指针 p 在 16 字节边界上对齐 iff ((unsigned long)p & 15) == 0。

类似的解决方案出现在这篇文章中。 如何检查指针是否指向正确对齐的内存位置?

有人可以澄清一下,这个铸造是如何工作的?我的意思是,如果我有一个void *ptr = CreateMem();,在我看来,(unsigned long)(ptr)指针本身会被重新解释为一个unsigned long. 为什么这个重新解释的 void 指针的值会对指向的内存的对齐有任何影响?

编辑:感谢所有有用的评论。请多容忍我一点。也许一个简化的例子可以帮助我更好地理解这一点。

#include <iostream>
using namespace std;
struct __attribute__((aligned(16))) Data0 {
  uint8_t a;  
};

struct Data1 {
  uint8_t a;  
};

int main() {
    std::cout<<sizeof(Data0) << "\n"; //< --(1)
    std::cout<<sizeof(Data1) << "\n";
    Data0 ptr0[10];
    Data1 ptr1[10];
    std::cout<<(unsigned long) (ptr0 + 1) - (unsigned long) ptr0   << "\n"; //< --- (2)
    std::cout<<(unsigned long) (ptr1 + 1) - (unsigned long) ptr1   << "\n";
    return 0;
}

迄今为止,我总是将对齐内存解释为具有以下两个要求。sizeof()应该返回指定大小的倍数(参见条件 (1))。并且在增加指向对齐结构数组的指针时,步幅最终也将是指定大小的倍数(参见条件(2))。

所以我有点惊讶地看到对实际值还有第三个要求ptr0。或者我可能完全误解了这一点。上面示例中指向的内存是否ptr0会被认为是对齐的,而不是这样ptr1

当我输入这个时,我意识到我实际上并不理解对齐内存本身的含义。由于我在分配 cuda 缓冲区时主要处理此问题,因此我倾向于将其与我的数据结构所需的某种填充相关联。

考虑第二个例子。那个aligned_allochttps://en.cppreference.com/w/c/memory/aligned_alloc

分配 size 字节的未初始化存储,其对齐方式由对齐指定。

我不确定如何解释这一点。说,我做了一个,与说where相比,void *p0 = aligned_alloc(16, 16 * 2)指向的内存有什么不同。p0p1std::vector<char> buffer(32); char *p1 = buffer.data()

标签: c++pointersc++17

解决方案


为什么这个重新解释的 void 指针的值会对指向的内存的对齐有任何影响?

因为在普通架构中,void 指针到 unsigned long 的转换是指针指向内存地址。或者由于转换为无符号类型的工作方式,如果地址的低位太大而无法放入无符号长整数中。测试对齐就足够了,因为在这种情况下,您只需要地址的最低位。

我不确定它是否真的是每个标准都规定的,因为如果将内存地址的实际表示留给实现,我可以记住 16 位英特尔处理器使用的段+地址表示。在那些架构中,可寻址内存使用 20 位,而远指针(能够表示任何地址)表示为 32 位无符号值,其中高 16 位是段,低 16 位是偏移量。然后将它们组合为:

Segment S S S S _    (4 * 4 = 16 bits)
Offset  _ O O O O    (4 * 4 = 16 bits)
Address A A A A A    (5 * 4 = 20 bits)

您可以看到这种架构不允许轻松测试高于 16 的对齐。例如,这个指针值 0x00010040 可以被 64 整除,但实际地址是 0x00050 (80),只能被 16 整除。

但是只有恐龙才能记住 Intel 8086 及其分段地址表示,即使使用它,测试最多 16 字节的对齐也很简单......


推荐阅读