【问题标题】:Casting a void pointer to check memory alignment铸造一个空指针来检查内存对齐
【发布时间】:2020-01-17 23:34:24
【问题描述】:

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

请参阅How to determine if memory is aligned?,它提出了具体声明。

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

类似的解决方案出现在这篇文章中。 How to check if a pointer points to a properly aligned memory location?

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

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

#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_alloc 的那个。 https://en.cppreference.com/w/c/memory/aligned_alloc

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

我不知道如何解释。说,我做了一个void *p0 = aligned_alloc(16, 16 * 2),与p1std::vector&lt;char&gt; buffer(32); char *p1 = buffer.data()相比,p0指向的内存有什么不同。

【问题讨论】:

  • 指针只是一个包含内存地址的数字。您应该使用uintptr_t 而不是unsigned long,因为前者保证足够大以容纳任何地址
  • @AlanBirtles 我知道指针只是一个数字。但是为什么这个数字对指向的内存的对齐有任何影响呢?
  • 如果数字是 16 的倍数,则它与 16 字节边界对齐
  • @AlanBirtles 我确定我在这里遗漏了一些 c++ 概念。但据我所知,我们有一个指针和内存指向。您声称的是指针值与它指向的内存的对齐方式有某种关系?它是否正确?这是 c++ 标准的一部分吗?
  • 对我来说,听起来你要问的是对齐的定义。正如 16 字节对齐需要一个 16 倍数的地址,因为这首先是 16 字节对齐的含义。不过,感觉就像我错过了这个问题的一些东西。

标签: c++ pointers c++17


【解决方案1】:

为什么这个重新解释的 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 个字节的对齐也很简单......

【讨论】:

    【解决方案2】:

    (unsigned long)(ptr) 将指针转换为无符号整数会返回指针的内存地址。

    p &amp; 15 等价于p % 16,即除以 16 的余数。如果为 0,则表示内存对齐为 16 的倍数。

    ((unsigned long)(ptr) &amp; 15) == 0 如果内存对齐为 16 的倍数,则返回 true。

    uintptr_t 是这种类型转换的更好类型,因为它应该更与平台无关。

    【讨论】:

    猜你喜欢
    • 2011-04-03
    • 2011-03-26
    • 1970-01-01
    • 2010-12-27
    • 2018-04-07
    • 1970-01-01
    • 1970-01-01
    • 2015-07-13
    • 1970-01-01
    相关资源
    最近更新 更多