使用从运算符 new 返回的指针作为指向数组元素的指针时是否存在未定义行为

jac*_*k X 5 c++ pointers language-lawyer c++17

int main(){\n  auto* ptr = (int*) ::operator new(sizeof(int)*10, std::align_val_t(alignof(int)));  //#1\n  ptr[1] = 4; //#a\n}\n
Run Code Online (Sandbox Code Playgroud)\n

考虑上面的代码,标准所说的内容如下:

\n

basic.stc.动态分配

\n
\n
    \n
  1. 返回的指针应适当对齐,以便可以将其转换为指向任何合适的完整对象类型([new.delete.single])的指针,然后用于访问分配的存储中的对象或数组
  2. \n
\n
\n

表达式.new#8

\n
\n
    \n
  1. 如果分配的类型是非数组类型,则分配函数的名称为operator new,释放函数的名称为operator delete。如果分配的类型是数组类型,则分配函数的名称为operator new[],释放函数的名称为operator delete[]。
  2. \n
\n
\n

表达式.new#1

\n
\n
    \n
  1. 如果实体是非数组对象,则 new 表达式返回指向所创建对象的指针。如果是数组,则 new 表达式返回指向数组初始元素的指针
  2. \n
\n
\n

关于指针算术的规则是:
\n expr.add#4

\n
\n
    \n
  1. 如果表达式 P 指向具有 n 个元素的数组对象 x 的元素 x[i],86 表达式 P + J 和 J + P(其中 J 的值为 j)指向(可能假设的)元素 x[i +j] 如果 0\xe2\x89\xa4 i+j \xe2\x89\xa4 n; 否则,行为是未定义的
  2. \n
\n
\n

所以,我想知道在 #a 处使用指针时是否是未定义的行为?我认为它违反了第 4 点。此外,从 std::allocate 的实现来看MSVC。似乎使用operator new()来分配空间并使用返回指针作为指向数组元素的指针。

\n

标准似乎没有说明直接调用 ::operator new(...) 时返回指针指向什么原始对象。它只是说调用此类分配函数所产生的返回指针可以转换为指向已适当对齐的对象的指针。

\n

更新:

\n

我关心的是数组的动态构造

\n

std::vector 的大多数实现都使用 std::allocate,并且 std::vector 有一个非静态数据成员记录 std::allocate 的结果。当使用 std::vector 的对象时arr[i],实现将使用非静态数据成员作为指向数组类型元素的指针来访问arr[i]。我觉得应该是UB吧?即,我们可以使用从分配函数返回的指针作为new-placement构造对象的操作数,但是如果我们使用指针访问ith对象或任何迭代器访问ith对象,这意味着它是UB?

\n

Oli*_*liv 4

表达方式:

::operator new(sizeof(int)*10, std::align_val_t(alignof(int)));
Run Code Online (Sandbox Code Playgroud)

是全局分配函数的函数调用表达式。它不使用new表达式来分配存储并构造对象或对象数组。全局分配器函数仅返回原始存储,并不在分配的内存中构造对象。

basic.stc.dynamic.allocation内部

  1. 返回的指针应适当对齐,以便可以将其转换为指向任何合适的完整对象类型([new.delete.single])的指针,然后用于访问分配的存储中的对象或数组[...]

该对象是一个不是隐式创建的对象。应该是根据代码中的[intro.object]/1创建的。

因此,在这种情况下,您知道该表达式在ptr[1]概念上有 2 个未定义的行为:


根据 c++20,这段代码具有明确定义的行为。因为隐式创建的类型的数组对象int[N]及其N>1元素也隐式创建将给出此代码定义的行为。

[介绍对象]/13

对名为operator new 或operator new[] 的函数的任何隐式或显式调用都会在返回的存储区域中隐式创建对象,并返回指向合适的已创建对象的指针。

[介绍对象]/10

某些操作被描述为在指定存储区域内隐式创建对象。对于指定为隐式创建对象的每个操作,该操作会在其指定的存储区域中隐式创建并启动零个或多个隐式生命周期类型 ([basic.types]) 的对象的生命周期,如果这样做会导致程序具有定义的行为。如果没有这样的对象集可以给程序提供定义的行为,则程序的行为是未定义的。如果多个这样的对象集将给予程序定义的行为,则未指定创建哪一个这样的对象集。

这两段话是语言指定方式的一场革命:

  • 发生的情况是不确定的,而是一个可能的集合隐式创建并开始零个或多个隐式生命周期类型对象的生命周期
  • 该约束不依赖于代码或程序执行中的给定点,而是依赖于整个程序执行:如果这样做会导致程序具有定义的行为

所以代码的有效性取决于归纳,我认为这是一场革命。例如,在上面的情况下,推理是:让我们假设分配函数调用将返回一个指向类型对象的指针int [1],因此代码的行为已定义,因此假设是正确的

但在整个程序执行之前,这个隐式对象仍然是假设的。例如,如果在代码中的其他地方int创建an ,则ptr[2]推理可以更改为:让我们假设分配函数调用将返回一个指向 类型对象的指针int [2],因此代码按照定义的行为进行,因此假设是正确的