最初是this question的主题,它出现了OP只是忽略了取消引用。与此同时,this answer让我和其他人思考 - 为什么允许使用C风格的演员或reinterpret_cast
投射指向引用的指针?
int main() {
char c = 'A';
char* pc = &c;
char& c1 = (char&)pc;
char& c2 = reinterpret_cast<char&>(pc);
}
上面的代码在Visual Studio上编译时没有任何警告或错误(关于演员),而GCC只会给你一个警告,如here所示。
我的第一个想法是指针以某种方式自动被取消引用(我正常使用MSVC,所以我没有得到GCC显示的警告),并尝试了以下内容:
#include <iostream>
int main() {
char c = 'A';
char* pc = &c;
char& c1 = (char&)pc;
std::cout << *pc << "\n";
c1 = 'B';
std::cout << *pc << "\n";
}
显示非常有趣的输出here。所以你似乎 访问指向变量,但与此同时,你不是。
想法?解释吗?标准报价?
答案 0 :(得分:35)
嗯,这就是reinterpret_cast
的目的!顾名思义,该转换的目的是将内存区域重新解释为另一种类型的值。因此,使用reinterpret_cast
,您始终可以将一种类型的左值转换为另一种类型的引用。
这在语言规范的5.2.10 / 10中有所描述。它还说reinterpret_cast<T&>(x)
与*reinterpret_cast<T*>(&x)
是一样的。
在这种情况下,您正在构建指针的事实完全并不完全无关紧要。不,指针不会自动解除引用(考虑到*reinterpret_cast<T*>(&x)
解释,甚至可能会说相反的情况:该指针的地址是自动获取的)。在这种情况下,指针只是“占据内存中某些区域的某个变量”。该变量的类型无论如何都没有区别。它可以是double
,指针,int
或任何其他左值。该变量仅被视为您将重新解释作为另一种类型的内存区域。
至于C风格的演员表 - 在这种情况下它只被解释为reinterpret_cast
,所以上面的内容立即适用于它。
在第二个示例中,您将引用c
附加到指针变量pc
占用的内存中。当您执行c = 'B'
时,强制将值'B'
写入该内存,从而完全破坏原始指针值(通过覆盖该值的一个字节)。现在,被破坏的指针指向一些不可预测的位置。后来你试图取消引用被破坏的指针。在这种情况下发生的事情是纯粹的运气。程序可能会崩溃,因为指针通常是不可分的。或者你可能会很幸运,并使你的指针指向一些不可预测但有效的位置。在这种情况下,程序将输出一些东西。没有人知道它会输出什么,并且没有任何意义。
可以将第二个程序重写为没有引用的等效程序
int main(){
char* pc = new char('A');
char* c = (char *) &pc;
std::cout << *pc << "\n";
*c = 'B';
std::cout << *pc << "\n";
}
从实际角度来看,在小端平台上,您的代码将覆盖指针的最低有效字节。这样的修改不会使指针指向太远离其原始位置。因此,代码更可能打印一些东西而不是崩溃。在big-endian平台上,你的代码会破坏指针中最重要的字节,从而将它抛向指向一个完全不同的位置,从而使你的程序更容易崩溃。
答案 1 :(得分:4)
我花了一段时间才弄清楚,但我想我终于明白了。
C ++标准指定强制转换reinterpret_cast<U&>(t)
等同于*reinterpret_cast<U*>(&t)
。
在我们的案例中,U
为char
,t
为char*
。
扩展这些,我们看到发生以下情况:
char**
类型的值。reinterpret_cast
此值为char*
char
左值。 reinterpret_cast
允许您从任何指针类型转换为任何其他指针类型。因此,从char**
到char*
的演员表格格式正确。
答案 2 :(得分:3)
我会尝试使用我对引用和指针的根深蒂固的直觉来解释这一点,而不是依赖于标准的语言。
嗯,&#34;&#34;&#34;从这个角度来看?或者更确切地说,你将如何实施&#34;一个参考?当然,用指针。因此,每当您在某些代码中看到引用时,您可以假装它只是一个以特殊方式使用的指针:如果int x;
和int& y{x};
那么我们真的有一个int* y_ptr = &x
;如果我们说y = 123;
,我们只是指*(y_ptr) = 123;
。这与我们使用C数组下标(a[1] = 2;
)时实际发生的情况没有什么不同,a
是&#34;衰变&#34;表示指向其第一个元素的指针,然后执行的是*(a + 1) = 2
。
(旁注:编译器实际上 总是在每个引用后面都有指针;例如,编译器可能会使用寄存器来引用变量,然后指针可以&# 39; t指向它。但这个比喻仍然非常安全。)
接受&#34;引用实际上只是伪装的指针&#34;现在,我们可以用reinterpret_cast<>()
忽略这种伪装,这应该不足为奇。
PS - std::ref
在您深入研究时也只是一个指针。
答案 3 :(得分:0)
当你进行投射,使用C风格的演员或使用reinterpret_cast时,你基本上是在告诉编译器看另一种方式(“你不介意,我知道我在做什么”)。
C ++允许您告诉编译器这样做。这并不意味着这是一个好主意......
答案 4 :(得分:0)
它被允许,因为C ++在你施放时几乎可以提供任何东西。
但至于行为:
由于指针的最后一个字节被修改,新的内存地址就在附近,因此不太可能存在于您的程序不允许访问的内存中。这就是为什么你没有得到seg-fault。获得的实际值是未定义的,但很可能是零,这解释了将其解释为char时的空白输出。