And*_*nga 25 c++ strict-aliasing type-punning
在一个特定的C++函数中,我碰巧有一个指向浮点缓冲区的指针,我想暂时用它来存储一半的双精度数.有没有一种方法可以使用此缓冲区作为临时空间来存储双打,这也是标准允许的(即,不是未定义的行为)?
总之,我想这样:
void f(float* buffer)
{
double* d = reinterpret_cast<double*>(buffer);
// make use of d
d[i] = 1.;
// done using d as scratch, start filling the buffer
buffer[j] = 1.;
}
Run Code Online (Sandbox Code Playgroud)
据我所知,没有简单的方法可以做到这一点:如果我理解正确,reinterpret_cast<double*>这样会因为类型别名而导致未定义的行为,并且memcpy在union不复制数据和分配额外空间的情况下无法使用浮点数/双数,这会失败在我的情况下,目的并且碰巧是昂贵的(并且在C++中不允许使用用于类型惩罚的联合).
可以假设浮动缓冲区已正确对齐以将其用于双精度.
phö*_*hön 10
我认为以下代码是一种有效的方法(它实际上只是一个关于这个想法的小例子):
#include <memory>
void f(float* buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// we have started the lifetime of the doubles.
// "d" is a new pointer pointing to the first double object in the array.
// now you can use "d" as a double buffer for your calculations
// you are not allowed to access any object through the "buffer" pointer anymore since the floats are "destroyed"
d[0] = 1.;
// do some work here on/with the doubles...
// conceptually we need to destory the doubles here... but they are trivially destructable
// now we need to start the lifetime of the floats again
new (buffer) float[10];
// here we are unsure about wether we need to update the "buffer" pointer to
// the one returned by the placement new of the floats
// if it is nessessary, we could return the new float pointer or take the input pointer
// by reference and update it directly in the function
}
int main()
{
float* floats = new float[10];
f(floats, sizeof(float) * 10);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
重要的是,您只能使用从placement new收到的指针.重要的是放置新的浮子.即使它是无操作结构,您也需要再次启动浮动的生命周期.
忘记std::launder并reinterpret_cast在评论中.Placement new将为您完成工作.
编辑:确保在main中创建缓冲区时正确对齐.
更新:
我只是想对评论中讨论的内容进行更新.
为此,我们可以a)通过引用传递浮点指针并更新它,或者b)从函数返回新获取的浮点指针:
一个)
void f(float*& buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// do some work here on/with the doubles...
buffer = new (buffer) float[10];
}
Run Code Online (Sandbox Code Playgroud)
b)
float* f(float* buffer, std::size_t buffer_size_in_bytes)
{
/* same as inital example... */
return new (buffer) float[10];
}
int main()
{
float* floats = new float[10];
floats = f(floats, sizeof(float) * 10);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
下一个更重要的事情是,placement-new允许有内存开销.因此,允许实现在返回的数组的前面放置一些元数据.如果发生这种情况,对我们记忆中适合多少双打的天真计算显然是错误的.问题是,我们不知道实现将事先为特定调用获取多少字节.但是,调整我们知道适合剩余存储空间的双打数量是很有必要的.这里(/sf/answers/610535271/)是另一个SO帖子,Howard Hinnant提供了一个测试片段.我使用在线编译器对此进行了测试,并发现对于简单的可破坏类型(例如双精度),开销为0.对于更复杂的类型(例如std :: string),存在8字节的开销.但是对于你的平台/编译器来说,这可能会有所不同.请事先用霍华德的片段进行测试.
对于为什么我们需要使用某种新的放置(通过new []或单个元素new)的问题:我们被允许以我们想要的每种方式来转换指针.但最终 - 当我们访问该值时 - 我们需要使用正确的类型来避免使用严格的别名规则.简单的说法:当指针类型的对象确实存在于指针给定的位置时,它只允许访问对象.那么如何将物体带入生活?标准说:
https://timsong-cpp.github.io/cppwp/intro.object#1:
"当隐式更改联合的活动成员或创建临时对象时,对象由定义,新表达式创建."
还有一个额外的部门可能看起来很有趣:
https://timsong-cpp.github.io/cppwp/basic.life#1:
"如果一个对象属于一个类或聚合类型,并且它或它的一个子对象是由除了一个普通的默认构造函数之外的构造函数初始化的,那么它就被认为是非空的初始化.T类型对象的生命周期开始于:
所以现在我们可能会争辩说,因为双打是微不足道的,我们是否需要采取一些行动来使琐碎的物体生活并改变实际的生物?我说是的,因为我们最初获得了浮点数的存储空间,并且通过双指针访问存储空间会违反严格的别名.所以我们需要告诉编译器实际类型已经改变.这整个最后一点3讨论很有争议.你可以形成自己的意见.您现在掌握了所有信息.
您可以通过两种方式实现这一目标.
第一:
void set(float *buffer, size_t index, double value) {
memcpy(reinterpret_cast<char*>(buffer)+sizeof(double)*index, &value, sizeof(double));
}
double get(const float *buffer, size_t index) {
double v;
memcpy(&v, reinterpret_cast<const char*>(buffer)+sizeof(double)*index, sizeof(double));
return v;
}
void f(float *buffer) {
// here, use set and get functions
}
Run Code Online (Sandbox Code Playgroud)
第二:float *你需要分配一个"无类型" char[]缓冲区,并使用placement new来放置浮点数或双精度数:
template <typename T>
void setType(char *buffer, size_t size) {
for (size_t i=0; i<size/sizeof(T); i++) {
new(buffer+i*sizeof(T)) T;
}
}
// use it like this: setType<float>(buffer, sizeOfBuffer);
Run Code Online (Sandbox Code Playgroud)
然后使用此访问器:
template <typename T>
T &get(char *buffer, size_t index) {
return *std::launder(reinterpret_cast<T *>(buffer+index*sizeof(T)));
}
// use it like this: get<float>(buffer, index) = 33.3f;
Run Code Online (Sandbox Code Playgroud)
第三种方式可能就像phön的答案(见我在该答案下的评论),遗憾的是由于这个问题,我无法做出正确的解决方案.
| 归档时间: |
|
| 查看次数: |
945 次 |
| 最近记录: |