[英]Why is it allowed to cast a pointer to a reference?
最初是这个问题的主题,它出现了OP只是忽略了dereference。 同时, 这个答案让我和其他一些人思考 - 为什么允许使用C风格的演员或reinterpret_cast
投射指向引用的指针?
int main() {
char c = 'A';
char* pc = &c;
char& c1 = (char&)pc;
char& c2 = reinterpret_cast<char&>(pc);
}
上面的代码在编译时没有在Visual Studio中的任何警告或错误(关于投),而GCC只会给你一个警告,如图所示这里 。
我的第一个想法是指针以某种方式自动被取消引用(我正常使用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";
}
这里显示了非常有趣的输出。 因此,您似乎正在访问指向变量,但与此同时,您不是。
想法? 解释吗? 标准报价?
嗯,这就是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";
}
从实际角度来看,在little-endian平台上,您的代码将覆盖指针的最低有效字节。 这样的修改不会使指针指向太远离其原始位置。 因此,代码更可能打印一些东西而不是崩溃。 在big-endian平台上,你的代码会破坏指针的最重要的字节,因此将它疯狂地指向一个完全不同的位置,从而使你的程序更容易崩溃。
我花了一段时间来理解它,但我想我终于明白了。
C ++标准指定reinterpret_cast<U&>(t)
等同于*reinterpret_cast<U*>(&t)
。
在我们的例子中, U
是char
, t
是char*
。
扩展这些,我们看到发生以下情况:
char**
类型的值。 reinterpret_cast
为char*
char
值。 reinterpret_cast
允许您从任何指针类型转换为任何其他指针类型。 因此,从char**
到char*
的演员char**
很好。
我将尝试使用我对引用和指针的根深蒂固的直觉来解释这一点,而不是依赖于标准的语言。
那么,从这个角度来看,什么是“参考”? 或者更确切地说,您将如何“实施”参考? 当然,用指针。 因此,无论何时在某些代码中看到引用,您都可以假装它只是一个以特殊方式使用的指针:if int x;
和int& y{x};
然后我们真的有一个int* y_ptr = &x
; 如果我们说y = 123;
我们只是说*(y_ptr) = 123;
。 当我们使用C数组下标( a[1] = 2;
)时,实际发生的事情是a
被“衰减”表示指向其第一个元素的指针,然后执行的是*(a + 1) = 2
。
(旁注:编译器实际上并不总是在每个引用后面都有指针;例如,编译器可能会使用寄存器来引用变量,然后指针不能指向它。但这个比喻仍然非常安全。)
接受了“引用实际上只是伪装的指针”这个比喻,现在我们可以用reinterpret_cast<>()
来忽略这个伪装,这一点不足为奇。
PS - std::ref
在你深入研究时也只是一个指针。
当你使用C风格的演员表或使用reinterpret_cast进行投射时,你基本上是在告诉编译器看另一种方式(“你不介意,我知道我在做什么”)。
C ++允许您告诉编译器这样做。 这并不意味着这是一个好主意......
它是允许的,因为C ++在你投射时几乎可以提供任何东西。
但至于行为:
由于指针的最后一个字节被修改,新的内存地址就在附近,因此不太可能存在于您的程序不允许访问的内存块中。 这就是为什么你没有得到seg-fault。 获得的实际值是未定义的,但很可能是零,这解释了将其解释为char时的空白输出。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.