数组与0元素的对齐

Com*_*sMS 22 c++ allocator language-lawyer c++11

C++ 允许动态分配零大小的数组:

int* p = new int[0];
delete[] p;
Run Code Online (Sandbox Code Playgroud)

我不能用这样的指针做很多事情(因为数组没有元素),但是新表达式需要给我一个有效的(!= nullptr)指针,然后我必须delete[]再次将它作为一个实际的数组.

有关这种新表达式返回的内存对齐的要求吗?考虑:

struct alignas(8) Foo {
    int x;
};

Foo* p = new Foo[0];
delete[] p;
Run Code Online (Sandbox Code Playgroud)

p保证指向8对齐的地址?此外,如果我编写自定义分配器,在这种情况下我是否需要返回指向对齐地址的指针?

gez*_*eza 8

N3337的basic.stc.dynamic.allocation/2(基本上是C++ 11):

分配功能尝试分配所请求的存储量.如果成功,它将返回存储块的起始地址,其长度以字节为单位应至少与请求的大小一样大.从分配函数返回时,分配的存储的内容没有限制.通过连续调用分配函数分配的存储的顺序,连续性和初始值是未指定的.返回的指针应适当对齐,以便可以将其转换为具有基本对齐要求(3.11)的任何完整对象类型的指针,然后用于访问分配的存储中的对象或数组(直到存储明确解除分配为止)调用相应的释放函数).即使请求的空间大小为零,请求也可能失败.如果请求成功,则返回的值应为非空指针值(4.10)p0,与之前返回的值p1不同,除非该值p1随后传递给运算符delete.取消引用作为零大小请求返回的指针的效果未定义.

基本对齐(basic.align/2):

基本对齐由小于或等于所有上下文中实现所支持的最大对齐的对齐表示,其等于alignof(std :: max_align_t)

扩展对齐(basic.align/3):

扩展对齐由大于alignof(std :: max_align_t)的对齐表示.

它是实现 - 定义是否支持任何扩展对齐以及支持它们的上下文

因此,返回的指针operator new必须具有基本对齐.即使指定了零尺寸.它是实现定义的,无论8是基本的还是扩展的对齐.如果它是基础,那就Foo没关系.如果它被扩展,那么它Foo是受支持的实现定义operator new.

注意,对于C++ 17,情况有所改善:


C++ 17的basic.stc.dynamic.allocation/2:

分配功能尝试分配所请求的存储量.如果成功,它将返回存储块的起始地址,其长度以字节为单位应至少与请求的大小一样大.从分配函数返回时,分配的存储的内容没有限制.未指定由连续调用分配函数分配的存储的顺序,连续性和初始值.返回的指针应适当对齐,以便它可以转换为指向任何合适的完整对象类型([new.delete.single])的指针,然后用于访问分配的存储中的对象或数组(直到存储明确通过调用相应的释放函数来解除分配).即使请求的空间大小为零,请求也可能失败.如果请求成功,则返回的值应为非空指针值([conv.ptr])p0,与先前返回的值p1不同,除非该值p1随后传递给运算符delete.此外,对于[new.delete.single]和[new.delete.array]中的库分配函数,p0应表示与调用者可访问的任何其他对象的存储不相交的存储块的地址.通过作为零大小请求返回的指针的间接效果是未定义的.

我强调相关部分.该句子意味着返回的指针void *operator new(...)应该具有合适的对齐方式.它没有提到零大小作为一种特殊情况(但是,当然,取消引用返回的指针是UB).

所以答案是平常的,没有特殊处理零:

  1. void *operator new(std::size_t) 必须返回一个对齐的指针 alignof(std?::?max_­align_­t)
  2. void *operator new(std::size_t, std::align_val_t align)必须返回一个对齐的指针align)

请注意,它是实现定义的,将调用哪个版本Foo.这取决于8是等于还是小于alignof(std?::?max_­align_­t).如果它更小,则调用第一个版本(因为它没有扩展对齐).否则第二个被叫.


更新:正如Massimiliano Janes的评论,这些段落适用于结果operator new,而不是新表达的结果.实现可以为结果添加任意偏移量operator new[].标准没有提到这个x偏移的价值:

new T [5]导致以下呼叫之一:

operator new [](sizeof(T)*5 + x)

operator new [](sizeof(T)*5 + x,std :: align_val_t(alignof(T)))

这里,x的每个实例都是一个非负的未指定值,表示数组分配开销; new-expression的结果将由operator new []返回的值抵消.这种开销可以应用于所有数组新表达式,包括那些引用库函数operator new [](std :: size_t,void*)和其他放置分配函数的表达式.开销的数量可能因新的一次调用而异.

但是,在我看来,这种x抵消不能是任意的.如果它不是对齐的倍数,则新表达式将返回非对齐指针(在所有情况下.不仅是零,而且还是非零大小参数).这显然不是我们想要的.

所以我认为这是标准中的漏洞.x应该将值约束为对齐的倍数(至少在非零分配情况下).但由于这种遗漏,似乎标准并不保证new[]表达式根本不返回对齐的指针(在非零情况下也是如此).

  • 我认为整个问题归结为一个问题:新表达式和分配函数是否需要*始终*导致*有效*指针值(即使在零数组情况下)?如果是,则应用[basic.compound#3],并且非空有效指针必须始终对齐(与它们指向的对象无关,如果有的话).就像你一样,我在两种情况下都倾向于积极,但我不知道...... :) (2认同)