const 正确清洗 pod(普通旧数据)
Posted
技术标签:
【中文标题】const 正确清洗 pod(普通旧数据)【英文标题】:const correct laundering of pods (plain old data) 【发布时间】:2018-03-11 06:05:27 【问题描述】:为了绕过别名和字节重新解释规则,我有一个名为T* landry_pod<T>(void*)
的实用函数,它假装复制字节并创建新对象。它在优化下编译成什么都没有,因为每个编译器都可以看到我将字节放回它们开始的地方。
template<class T>
T* laundry_pod( void* data )
static_assert( std::is_pod<T> ); // or more verbose replacement as pod is gone
char tmp[sizeof(T)];
std::memcpy( tmp, data, sizeof(T) );
T* r = ::new(data) T;
std::memcpy( data, tmp, sizeof(T) );
return r;
这确保sizeof(T)
位数据点相同,但返回指向T
类型对象的指针。当data
仅指向位但不是实际对象时,这是一种符合标准的T* r = (T*)data;
方式。它在运行时优化到 0 条指令。
遗憾的是,虽然它在运行时什么都不做,但从逻辑上讲,它不能用于const
缓冲区。
这是我尝试修改它以使用const
输入和输出:
template<class T, std::enable_if_t<std::is_const<T>, bool> = true, class In>
T* laundry_pod( const In* data )
static_assert( sizeof(In)==1 ); // really, In should be byte or char or similar
static_assert( std::is_pod<T> ); // or more verbose replacement as pod is gone
std::byte tmp[sizeof(T)];
std::memcpy( tmp, data, sizeof(T) ); // copy bytes out
for(std::size_t i =0; i<sizeof(T); ++i)
data[i].~In(); // destroy const objects there // is this redundant?
auto* r = ::new( (void*)data ) std::remove_const_t<T>; // cast away const on data (!)
std::memcpy( r, tmp, sizeof(T) ); // copy same bytes back
return r;
在这里我销毁 const 对象(以及字节),然后在它们的位置构造一个新对象。
上面应该优化到0条指令(试试看),但是我在创建r
时不得不抛弃const。
如果data
指向一个连续的 const char 或 byte 缓冲区,那么销毁这些对象是否足以让我重用存储空间并保持在定义的行为范围内?还是仅仅创建新对象就足够了?还是我注定要失败?
假设没有人事先使用旧的In
指针来访问原始字节。
【问题讨论】:
不清楚这个函数到底应该完成什么。 @nicol 通过添加非常量版本进行了澄清。 【参考方案1】:您问题的核心是重用const
对象的存储定义的行为吗?答案是否定的,根据basic.life#9:
在具有静态、线程或自动存储持续时间的 const 对象占用的存储位置,或者在此类 const 对象在其生命周期结束之前曾经占用的存储位置创建新对象会导致未定义的行为。
[ 例子:
struct B B(); ~B(); ; const B b; void h() b.~B(); new (const_cast<B*>(&b)) const B; // undefined behavior
— 结束示例 ]
表面上这是因为数据可能被放置在只读内存中。因此,尝试修改 const
数据是无操作的也就不足为奇了。
【讨论】:
数据可能根本不会放在任何地方。 (你确定你的意思是“表面上”吗?merriam-webster.com/dictionary/ostensibly) 在某些情况下确实可以对其进行优化,并且根本不会将任何内容放入二进制文件中;然而,考虑到问题的上下文,假设是有一些存储空间可供使用。 @user 不是真的,不必存储的事实也是主题;即使使用了 ODR,产生的身份仍然可能是假的。但这直接回答了我的问题。【参考方案2】:添加到其他答案中,原始函数实际上根本不是身份 - 您仍然会导致原始缓冲区的读取和写入,这可能与并发使用场景中的普通读取。特别是多个线程在同一个buffer上同时调用laundry_pod
就是UB。
【讨论】:
以上是关于const 正确清洗 pod(普通旧数据)的主要内容,如果未能解决你的问题,请参考以下文章