C++ 重新解释强制转换与严格别名
我读过关于严格别名的文章,但它仍然有点模糊,我永远不知道定义/未定义行为的界限在哪里。我发现的最详细的内容集中在C上。因此,如果您能告诉我这是否允许,以及自C++98/11/…以来发生了什么变化,那将是一件好事C++ 重新解释强制转换与严格别名,c++,language-lawyer,strict-aliasing,reinterpret-cast,C++,Language Lawyer,Strict Aliasing,Reinterpret Cast,我读过关于严格别名的文章,但它仍然有点模糊,我永远不知道定义/未定义行为的界限在哪里。我发现的最详细的内容集中在C上。因此,如果您能告诉我这是否允许,以及自C++98/11/…以来发生了什么变化,那将是一件好事 #include <iostream> #include <cstring> template <typename T> T transform(T t); struct my_buffer { char data[128]; un
#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';
}
#包括
#包括
模板T变换(T);
构造我的缓冲区{
字符数据[128];
未签名pos;
my_buffer():pos(0){}
void rewind(){pos=0;}
模板无效通过指针推送(常量T&T){
*重新解释(和数据[pos])=transform(t);
pos+=sizeof(T);
}
通过指针投射的模板无效弹出(T&T){
t=转换(*重新解释转换(&data[pos]);
pos+=sizeof(T);
}
};
//实际上在这里做一些实变换(实际上还需要一个逆)
//ie这限制了T的允许类型
模板int变换(int x){return x;}
模板双变换(双x){return x;}
int main(){
我的b;
b、 通过鼠标指针按下鼠标(1);
b、 通过鼠标指针按下鼠标(2.0);
b、 倒带();
int x;
双y;
b、 通过指针投射的pop_(x);
b、 通过指针投射的pop_(y);
标准::cout简短回答:
在指向的地址处构造了类型为T
的对象之前,您不能执行此操作:*重新解释\u cast(&data[pos])=
。您可以通过放置新对象来完成此操作
即使如此,对于C++17及更高版本,您可能需要使用std::launder
,因为您可以通过char*
类型的指针&data[pos]
访问创建的对象(类型T
)
仅在某些特殊情况下才允许“直接”reinterpret\u cast
,例如,当T
为std::byte
、char
或无符号char
时
在C++17之前,我会使用基于memcpy
的解决方案。编译器可能会优化掉任何不必要的副本
我知道char*
可以指向任何东西,但我还有一个T*
可以指向char*
是的,这是一个问题。虽然指针强制转换本身定义了行为,但使用它来访问类型为T
的不存在的对象不是
不像C,C++不允许即兴创建对象*.不能简单地将某些内存位置分配为类型<代码> t>代码>,并创建一个对象类型,需要一个对象的类型已经存在.这需要放置<代码>新< /C> > .以前的标准在它上是含糊的,但是目前,每个[对象.对象]:
1[…]在隐式更改联合的活动成员(12.3)或创建临时对象(7.4,15.2)时,通过定义(6.1)、新表达式(8.3.4)创建对象。[…]
由于您没有执行任何这些操作,因此不会创建任何对象
此外,C++不隐含地考虑指向同一个地址的不同对象的指针。
计算指向char
对象的指针。将其强制转换为T*
不会使其指向驻留在该地址的任何T
对象,并且取消引用该指针具有未定义的行为。C++17添加了一种方法,可以让编译器知道您希望在该地址访问与您所访问的对象不同的对象有一个指向的指针
当您修改代码以使用placementnew
和std::launder
时,并确保没有未对齐的访问(我认为您为了简洁起见忽略了这一点),您的代码将定义行为
*在C++的未来版本中允许讨论这个问题。
< P>混叠是指当两个对象引用同一个对象时的情况。这可能是引用或指针。< /P>int x;
int* p = &x;
int& r = x;
// aliases: x, r и *p refer to same object.
对于编译器来说,重要的是,如果一个值是使用一个名称编写的,那么它可以通过另一个名称访问
int foo(int* a, int* b) {
*a = 0;
*b = 1;
return *a;
// *a might be 0, might be 1, if b points at same object.
// Compiler can't short-circuit this to "return 0;"
}
现在,如果指针是不相关的类型,编译器没有理由期望它们指向相同的地址。这是最简单的UB:
int foo( float *f, int *i ) {
*i = 1;
*f = 0.f;
return *i;
}
int main() {
int a = 0;
std::cout << a << std::endl;
int x = foo(reinterpret_cast<float*>(&a), &a);
std::cout << a << "\n";
std::cout << x << "\n"; // Surprise?
}
// Output 0 0 0 or 0 0 1 , depending on optimization.
Reinterpret cast也不会创建它们指向的对象,并且为不存在的对象赋值的对象是UB,所以如果它指向的类不是琐碎的,也不能使用cast的解引用结果来存储数据。Whoa,1.可以。这两者之间的区别是什么:
int*a=Reinterpret\u cast(malloc(sizoof(int)));*a=5;
和charbuf[sizeof(int)];int*a=reinterpre_cast(buf)* *=5;?@ kAMILUCK否,C++是明确地创建对象的时候。赋值不隐式创建一个对象。好的,谢谢!需要再深入研究这个话题。@ KamilCuk,这不是一个简单的话题:例如,参见这个答案的底部部分。e> 如果你尝试访问聚合类型,例如数组或结构的数组,我会有自己的限制。为了简洁起见,我省略了memcpy解决方案。std::launder
对我来说是全新的;)思考之后,我不再确定你的论证是否能说服我。使用memcpy
而不是强制转换如何?在这种情况下,我是一个也没有真正创建一个T
的实例来代替数据
,但是如果使用memcpy
@user463035818就没有问题了,但是在这种情况下,通过不像访问T
那样访问它,您就不需要在那里有一个T
类型的对象。这是有意义的。因此在char
数组刚好有一些位可以被T
记忆复制(因为我在那个地方复制了T
的位)但不是一个我可以通过cast检索的T
,因为这种语言要求,如果我将一些内存视为存在T
类型的对象,我必须首先在该位置创建一个T
类型的对象。@user463035818
char* pc = new char[100]{1,2,3,4,5,6,7,8,9,10}; // Note, initialized.
int* pi = reinterpret_cast<int*>(pc); // no problem.
int i = *pi; // UB
char* pc2 = reinterpret_cast<char*>(pi+2));
char c = *pc2; // no problem, unless increment didn't put us beyond array bound.