#include <iostream> #include <cstring> template <typename T> T transform(T t); struct my_buffer { char data[128]; unsigned pos; my_buffer() : pos(0) {} void rewind() { pos = 0; } template <typename T> void push_via_pointer_cast(const T& t) { *reinterpret_cast<T*>(&data[pos]) = transform(t); pos += sizeof(T); } template <typename T> void pop_via_pointer_cast(T& t) { t = transform( *reinterpret_cast<T*>(&data[pos]) ); pos += sizeof(T); } }; // actually do some real transformation here (and actually also needs an inverse) // ie this restricts allowed types for T template<> int transform<int>(int x) { return x; } template<> double transform<double>(double x) { return x; } int main() { my_buffer b; b.push_via_pointer_cast(1); b.push_via_pointer_cast(2.0); b.rewind(); int x; double y; b.pop_via_pointer_cast(x); b.pop_via_pointer_cast(y); std::cout << x << " " << y << '\n'; }
请不要过分关注可能的越界访问以及可能没有必要写这样的事实.我知道char *可以指向任何东西,但我也有一个指向char *的T *.也许还有一些我想念的东西.
这是一个complete example还包括通过memcpy推送/弹出,其中afaik不受严格别名的影响.
TL; DR:上面的代码是否表现出未定义的行为(暂时忽略了一个越界的访问),如果是,为什么? C 11或其中一个较新的标准有什么变化吗?
解决方法
I know that
char*
is allowed to point to anything,but I also have aT*
that points to achar*
.
对,这是一个问题.虽然指针强制转换本身已定义了行为,但使用它来访问类型为T的不存在的对象则不然.
与C不同,C不允许即兴创建对象*.您不能简单地将某个内存位置指定为类型T并且创建该类型的对象,您需要具有该类型的对象.这需要放置新的.以前的标准很不明确,但目前,每[intro.object]:
1 […] An object is created by a definition (6.1),by a new-expression (8.3.4),when implicitly changing the active member of a union (12.3),or when a temporary object is created (7.4,15.2). […]
由于您没有执行任何这些操作,因此不会创建任何对象.
此外,C不会隐含地考虑指向同一地址的不同对象的指针.你的& data [pos]计算一个指向char对象的指针.将其强制转换为T *并不会使其指向驻留在该地址的任何T对象,并且取消引用该指针具有未定义的行为. C 17增加了std::launder
,这是让编译器知道你想要访问该地址不同于对象的另一个对象的方法.
当您修改代码以使用placement new和std :: launder时,并确保您没有错位访问(我假设您为了简洁而将其留下),您的代码将具有已定义的行为.
*有关在未来版本的C中允许这样做的讨论.