C++ 的新操作是不是可以保证地址返回的对齐?

Posted

技术标签:

【中文标题】C++ 的新操作是不是可以保证地址返回的对齐?【英文标题】:Is there any guarantee of alignment of address return by C++'s new operation?C++ 的新操作是否可以保证地址返回的对齐? 【发布时间】:2010-10-05 03:00:50 【问题描述】:

大多数有经验的程序员都知道数据对齐对程序的性能很重要。我见过一些程序员编写的程序分配了比他们需要的更大的缓冲区,并使用对齐的指针作为开始。我想知道我是否应该在我的程序中这样做,我不知道 C++ 的新操作返回的地址是否对齐有任何保证。于是我写了一个小程序来测试

for(size_t i = 0; i < 100; ++i) 
    char *p = new char[123];
    if(reinterpret_cast<size_t>(p) % 4) 
        cout << "*";
        system("pause");
    
    cout << reinterpret_cast<void *>(p) << endl;

for(size_t i = 0; i < 100; ++i) 
    short *p = new short[123];
    if(reinterpret_cast<size_t>(p) % 4) 
        cout << "*";
        system("pause");
    
    cout << reinterpret_cast<void *>(p) << endl;

for(size_t i = 0; i < 100; ++i) 
    float *p = new float[123];
    if(reinterpret_cast<size_t>(p) % 4) 
        cout << "*";
        system("pause");
    
    cout << reinterpret_cast<void *>(p) << endl;

system("pause");

我使用的编译器是 Visual C++ Express 2008。看来新操作返回的所有地址都是对齐的。但我不确定。所以我的问题是:有任何保证吗?如果他们有保证,我不必调整自己,如果没有,我必须。

【问题讨论】:

【参考方案1】:

C++17 改变了对new 分配器的要求,因此它需要返回一个对齐等于宏__STDCPP_DEFAULT_NEW_ALIGNMENT__ 的指针(由实现定义,而不是通过包含标头) .

这很重要,因为此大小可能大于alignof(std::max_align_t)。例如,在 Visual C++ 中,最大常规对齐是 8 字节,但默认的 new 总是返回 16 字节对齐的内存。

另外,请注意,如果您使用自己的分配器覆盖默认的 new,您也要求遵守 __STDCPP_DEFAULT_NEW_ALIGNMENT__

【讨论】:

我的意思是,您始终可以在明确提供对齐方式的地方调用重载。 new 在类型具有新扩展对齐时自动执行。 “另外,请注意,如果您使用自己的分配器覆盖默认的 new,您也需要遵守 STDCPP_DEFAULT_NEW_ALIGNMENT。”这是什么来源?【参考方案2】:

对齐具有来自标准 (3.7.3.1/2) 的以下保证:

返回的指针应适当对齐,以便可以将其转换为 任何完整对象类型的指针,然后用于访问 分配的存储空间(直到 存储通过调用相应的释放函数显式释放)。

编辑:感谢 timday 在 gcc/glibc 中突出显示一个 bug 保证不成立。

编辑 2:Ben 的评论突出了一个有趣的边缘案例。分配程序的要求仅适用于标准提供的要求。如果应用程序有自己的版本,那么结果就没有这样的保证。

【讨论】:

理论上。实际上,如果您在 32 位系统上使用 gcc+glibc 和 SSE 类型,请注意gcc.gnu.org/bugzilla/show_bug.cgi?id=15795。 @BenVoigt:你有更多细节吗?你是说有保证,但在别处给出?我的草案(N3337)在 5.3.4/10 下讨论了返回(无符号)char 数组的要求,并有一条评论开头:“因为假设分配函数返回指向存储的指针,该指针针对以下对象进行了适当对齐任何具有基本对齐方式的类型..."。 @RichardCorden:分配函数operator new[]()返回的指针对齐良好。但是new T[n] 不需要也通常不会返回operator new[](n * sizeof (T))。几乎每个编译器都会在前面添加元数据来跟踪元素的数量(以便调用正确数量的析构函数),并且在前面添加标头显然会改变对齐方式。 @BenVoigt:恕我直言,添加 5.3.4/10 突出了委员会为明确保证在所有(法律)案件中正确对齐所做的努力。编译器可以轻松地将元数据添加到数组的前面并留下空隙。进一步阅读,5.3.4/5 有:the new-expression yields a pointer to the initial element (if any) of the array。所以new[] 正在返回数组的元素——而不仅仅是一个内存位置。因此,它必须具有正确的对齐方式等。 @Richard:另一件事是您从new[] 获得的最小正确对齐与最佳对齐不同。出于性能原因,您希望某些结构与缓存对齐。因此,即使您更新答案以引用标准的正确部分,它仍然不能解决所提出的问题。此外,一些指令(尤其是 SSE)需要更严格的对齐。【参考方案3】:

这是一个较晚的答案,但只是为了澄清 Linux 上的情况 - 在 64 位系统上 内存始终是 16 字节对齐的:

http://www.gnu.org/software/libc/manual/html_node/Aligned-Memory-Blocks.html

在 GNU 系统中 malloc 或 realloc 返回的块地址总是 八的倍数(或 64 位系统上的十六)。

new 运算符在内部调用 malloc (见./gcc/libstdc++-v3/libsupc++/new_op.cc) 所以这也适用于new

malloc 的实现是glibc 的一部分,它基本上定义了 MALLOC_ALIGNMENT2*sizeof(size_t)size_t 是 32bit=4byte 和 64bit=8byte 分别在 x86-32 和 x86-64 系统上。

$ cat ./glibc-2.14/malloc/malloc.c:
...
#ifndef INTERNAL_SIZE_T
#define INTERNAL_SIZE_T size_t
#endif
...
#define SIZE_SZ                (sizeof(INTERNAL_SIZE_T))
...
#ifndef MALLOC_ALIGNMENT
#define MALLOC_ALIGNMENT       (2 * SIZE_SZ)
#endif

【讨论】:

【参考方案4】:

顺便说一句,MS documentation 提到了一些关于 malloc/new 返回地址的内容,它们是 16 字节对齐的,但从实验来看,情况并非如此。我碰巧需要一个项目的 16 字节对齐(通过增强指令集加速内存复制),最后我求助于编写自己的分配器...

【讨论】:

【参考方案5】:

我在一个系统上工作,他们使用对齐来释放奇数位供自己使用!

他们使用奇数位来实现虚拟内存系统。

当一个指针设置了奇数位时,他们用它来表示它指向(减去奇数位) 位)到信息以从数据库中获取数据而不是数据本身。

我认为这是一种特别讨厌的编码,它本身就很聪明!

托尼

【讨论】:

它们被称为标记指针,它们并不少见。许多编程语言实现都使用这个技巧来区分指针和整数。 并且 ARM 互通使用它 - 在适用的情况下,ARM 模式代码地址是偶数,拇指模式地址是奇数。我见过一个 AVL 树实现,它使用底部两位来存储节点子树的高度差。在有限的系统上,你可以在任何地方填充标志位:-) 在 MAC OS (Classic) 的早期版本中,他们使用前 8 位作为内存管理器。 68000 的 ptrs 最多只有 24 位,而地址寄存器是 32 位。【参考方案6】:

平台的 new/new[] 运算符将返回具有足够对齐的指针,以便它在基本数据类型(double、float 等)中表现良好。至少任何明智的 C++ 编译器+运行时都应该这样做。

如果您对 SSE 有特殊的对齐要求,那么使用特殊的aligned_malloc 函数可能是个好主意,或者使用您自己的函数。

【讨论】:

以上是关于C++ 的新操作是不是可以保证地址返回的对齐?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 在 main() 结束时返回 [关闭]

c++中的uint是为了保证返回值为正数使用的么,还是在啥情况下使用

C++验证redis返回的数据是不是为空

如果进程存在,kill(pid, 0) 是不是保证返回 0?

在 C 和 C++ 中返回 void 类型

取消引用结构会返回结构的新副本吗?