首页 > 解决方案 > 将基本类型数组中的内存重用于不同(但仍然是基本)类型数组是否合法

问题描述

这是关于内存重用的另一个问题的后续。由于最初的问题是关于特定实现的,因此答案与该特定实现有关。

所以我想知道,在符合要求的实现中,为提供的不同类型的数组重新使用基本类型数组的内存是否合法:

我以以下示例代码结束:

#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;
}

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

浮点对象确实重用了 int 对象的内存,因此 int 的生命周期在浮点数的生命周期开始时结束,因此 stric-aliasing 规则应该不是问题。数组是动态分配的,因此对象( int和 floats)实际上都是在operator new. 所以我认为一切都应该没问题。

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

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

免责声明:我建议不要在可移植代码库中使用此代码,这确实是一个语言律师问题。

标签: c++castinglanguage-lawyerstrict-aliasing

解决方案


int *in = reinterpret_cast<int *>(buffer); // Defined behaviour because alignment is ok

正确的。但可能不是你所期望的。[expr.static.cast]

“pointer to cv1 void”类型的纯右值可以转换为“pointer to cv2 T”类型的纯右值,其中T是一个对象类型,cv2与 cv-qualification 相同或大于 cv-qualification cv1。如果原始指针值表示A内存中某个字节的地址,并且A不满足 的对齐要求T,则生成的指针值未指定。否则,如果原始指针值指向一个 object a,并且有一个b类型的对象T(忽略 cv 限定)可以与 进行指针互转换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

是不正确的。[介绍对象]

当隐式更改联合的活动成员或创建临时对象时,对象由定义、new 表达式创建。

明显缺席的是作业。没有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;,否则,行为未定义。

假设元素是指一个过去的(假设的)元素。请注意,指向恰好与另一个对象位于相同地址位置的一个结束元素的指针并不指向该另一个对象。


推荐阅读