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_ALIGNMENT
是 2*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++中的uint是为了保证返回值为正数使用的么,还是在啥情况下使用