从基本类型数组中为不同(但仍然是基础)类型的数组重用内存是否合法

Ser*_*sta 15 c++ casting strict-aliasing language-lawyer

这是关于内存重用的另一个问题的后续内容.由于最初的问题是关于具体实施,答案与具体实施有关.

所以我想知道,在一致的实现中,为一个不同类型的数组重用基本类型数组的内存是否合法:

  • 这两种类型都是基本类型,因此具有普通的dtor和默认的ctor
  • 两种类型都有相同的大小和对齐要求

我结束了以下示例代码:

#include <iostream>

constexpr int Size = 10;

void *allocate_buffer() {
    void * buffer = operator new(Size * sizeof(int), std::align_val_t{alignof(int)});
    int *in = reinterpret_cast<int *>(buffer); // Defined behaviour because alignment is ok
    for (int i=0; i<Size; i++) in[i] = i;  // Defined behaviour because int is a fundamental type:
                                           // lifetime starts when is receives a value
    return buffer;
}
int main() {
    void *buffer = allocate_buffer();        // Ok, defined behaviour
    int *in = static_cast<int *>(buffer);    // Defined behaviour since the underlying type is int *
    for(int i=0; i<Size; i++) {
        std::cout << in[i] << " ";
    }
    std::cout << std::endl;
    static_assert(sizeof(int) == sizeof(float), "Non matching type sizes");
    static_assert(alignof(int) == alignof(float), "Non matching alignments");
    float *out = static_cast<float *>(buffer); //  (question here) Declares a dynamic float array starting at buffer
    // std::cout << out[0];      // UB! object at &out[0] is an int and not a float
    for(int i=0; i<Size; i++) {
        out[i] = static_cast<float>(in[i]) / 2;  // Defined behaviour, after execution buffer will contain floats
                                                 // because float is a fundamental type and memory is re-used.
    }
    // std::cout << in[0];       // UB! lifetime has ended because memory has been reused
    for(int i=0; i<Size; i++) {
        std::cout << out[i] << " ";         // Defined behaviour since the actual object type is float *
    }
    std::cout << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我添加了注释,解释了为什么我认为此代码应该定义了行为.恕我直言一切都很好,AFAIK标准符合,但我无法找到这里标记问题的行是否有效.

Float对象会重复使用int对象的内存,因此当浮点数的生命周期开始时,int的生命周期结束,因此stric-aliasing规则应该不是问题.数组是动态分配的,因此对象(int和float)实际上都是在返回的void类型数组中创建的operator new.所以我认为一切都应该没问题.

但由于它允许在现代C++中通常不赞成的低级对象替换,我必须承认我有一个疑问......

所以问题是:上面的代码是否调用UB,如果是,在哪里以及为什么?

免责声明:我建议在可移植的代码库中反对此代码,这实际上是一个语言律师问题.

Pas*_* By 9

int *in = reinterpret_cast<int *>(buffer); // Defined behaviour because alignment is ok
Run Code Online (Sandbox Code Playgroud)

正确.但可能不是你所期望的那种意义.[expr.static.cast]

"指向cv1 void"的类型的prvalue可以转换为"指向cv2 T" 的类型的prvalue ,其中T是一个对象类型,cv2与cv-qualification相同,或者cv-qualification比cv1.如果原始指针值表示A内存中字节的地址并且A不满足对齐要求T,则未指定结果指针值.否则,如果原始指针值指向一个对象a,并且存在一个b类型T(忽略cv-qualification)的对象,该对象是指针可互换的a,则结果是指向的对象b.否则,转换指针值不变.

没有int任何指针可互换的对象buffer,因此指针值不变.in是指向int*原始内存区域的类型指针.

for (int i=0; i<Size; i++) in[i] = i;  // Defined behaviour because int is a fundamental type:
                                       // lifetime starts when is receives a value
Run Code Online (Sandbox Code Playgroud)

是不正确的.[intro.object]

当隐式更改union的活动成员或创建临时对象时,对象由定义,new-expression创建.

明显缺席的是作业.没有int创建.实际上,通过消除,in是一个无效的指针,并且取消引用它是UB.

后者float*也跟随UB.

即使在没有通过正确使用new (pointer) Type{i};创建对象的所有上述UB的情况下,也不存在数组对象.(无关的)对象恰好在内存中并排发生.这意味着指针运算与结果指针也是UB.[expr.add]

当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型.如果表达式P指向具有元素x[i]的数组对象xn元素,则表达式P + JJ + P(其中J具有值j)指向(可能是假设的)元素,x[i+j] if 0 ? i+j ? n;否则,行为是未定义的.同样,表达式P - J指向(可能是假设的)元素,x[i?j] if 0 ? i?j ? n;否则行为是未定义的.

假设元素指的是一个过去(假设)元素.请注意,指向碰巧与另一个对象位于同一地址位置的一个past-the-end元素的指针不指向该另一个对象.

  • 请注意,您可以*大多数*使用`:: new(static_cast <void*>(buffer + i))int {i};`而不是赋值来修复该行.仍有"连续对象不是阵列制造"的标准缺陷. (2认同)

eer*_*ika 5

Passer By的答案涵盖了示例程序具有未定义行为的原因.我将尝试回答如何在没有最小UB的UB的情况下重用存储(在标准C++中,对于阵列存储的重用在技术上是不可能的,因为标准的当前措辞,所以为了实现重用,程序员必须依赖于实现"做正确的事").

转换指针不会自动显示对象.您必须首先构造浮动对象.这将开始它们的生命周期并结束int对象的生命周期(对于非平凡的对象,需要首先调用析构函数):

for(int i=0; i<Size; i++)
    new(in + i) float;
Run Code Online (Sandbox Code Playgroud)

您可以使用placement new返回的指针(在我的示例中被丢弃)直接使用新构造的float对象,或者您可以std::launder使用buffer指针:

float *out = std::launder(reinterpret_cast<float*>(buffer));
Run Code Online (Sandbox Code Playgroud)

然而,这是 许多更典型重用类型的存储unsigned char(或std::byte)而不是存储int对象.