将基本类型数组中的内存重用于其他(但仍是基本类型)数组是否合法? [英] Is it legal to reuse memory from a fundamental type array for a different (yet still fundamental) type array

查看:70
本文介绍了将基本类型数组中的内存重用于其他(但仍是基本类型)数组是否合法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是关于内存重用的其他问题的后续措施.由于最初的问题与特定的实现有关,所以答案与该特定的实现有关.

This is a follow up to this other question about memory re-use. As the original question was about a specific implementation, the answer was related to that specific implementation.

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

So I wonder whether, it is legal in a conformant implementation to re-use the memory of an array of a fundamental type for an array of a different type provided:

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

我以以下示例代码结尾:

I ended with the following example code:

#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标准,但是我找不到标记为 question 的行是否有效.

I have added comments explaining why I think that this code should have defined behaviour. And IMHO everything is fine and AFAIK standard conformant, but I was not able to find whether the line marked question here is or not valid.

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

Float objects do re-use memory from int objects, so life time of the ints end when life time of the floats start, so the stric-aliasing rule should not be a problem. Array was dynamically allocated so objects (int and floats) are in fact all created in a void type array returned by operator new. So I think that everything should be ok.

但是由于它允许低级对象替换,这在现代C ++中通常是不受欢迎的,所以我必须承认我有一个疑问...

But as it allows for low level object replacement which is normally frowned upon in modern C++ I must acknowledge I have a doubt...

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

So the question is: does above code invokes UB and if yes where and why?

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

Disclaimer: I would advise against this code in a portable code base, and this is really a language lawyer question.

推荐答案

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

正确.但可能并非您所期望的那样. [expr.static.cast]

Correct. But probably not in the sense you'd expect. [expr.static.cast]

指向cv1 void的指针"类型的prvalue可以转换为指向cv2 T的指针"类型的prvalue,其中T是对象类型,而cv2是与cv相同的限定词,或具有比cv1更高的cv资格.如果原始指针值表示内存中字节的地址A,并且A不满足T的对齐要求,则未指定结果指针值.否则,如果原始指针值指向对象a,并且存在类型为T(忽略cv限定)的对象b,该对象可与a进行指针互转换,则结果为指向的指针. b.否则,转换后指针值将保持不变.

A prvalue of type "pointer to cv1 void" can be converted to a prvalue of type "pointer to cv2 T", where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

buffer处没有int或指针可相互转换的对象,因此指针值保持不变. in是类型为int*的指针,指向原始内存区域.

There is no int nor any pointer-interconvertible object at buffer, therefore the pointer value is unchanged. in is a pointer of type int* that points to a region of raw memory.

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

不正确. [intro.object]

在隐式更改联合的活动成员时或在创建临时对象时,将通过定义,新表达式创建对象.

An object is created by a definition, by a new-expression, when implicitly changing the active member of a union, or when a temporary object is created.

值得注意的是分配.没有创建int.实际上,通过消除,in无效的指针,并取消引用它是UB.

Noticeably absent is assignment. No int is created. In fact, by elimination, in is an invalid pointer, and dereferencing it is UB.

后面的float*也都作为UB.

即使没有通过正确使用new (pointer) Type{i};创建对象的所有前述UB,也不存在 array 对象.这些(不相关的)对象恰好在内存中并排放置.这意味着指针算术与结果指针也为UB. [expr.add]

Even in absence of all the aforementioned UB by proper use of new (pointer) Type{i}; to create objects, there is no array object in existence. The (unrelated) objects just happens to be side by side in memory. This means pointer arithmetic with the resulting pointer is also UB. [expr.add]

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

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0 ≤ i+j ≤ n; otherwise, the behavior is undefined. Likewise, the expression P - J points to the (possibly-hypothetical) element x[i−j] if 0 ≤ i−j ≤ n; otherwise, the behavior is undefined.

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

Where hypothetical element refers to the one past-the-end (hypothetical) element. Note that a pointer to a one past-the-end element that happens to be at the same address location as another object doesn't point to that other object.

这篇关于将基本类型数组中的内存重用于其他(但仍是基本类型)数组是否合法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆