为什么允许将指针强制转换为引用?

时间:2011-05-07 21:57:47

标签: c++ pointers casting reference

最初是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。所以你似乎 访问指向变量,但与此同时,你不是。

想法?解释吗?标准报价?

5 个答案:

答案 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)

在我们的案例中,Uchartchar*

扩展这些,我们看到发生以下情况:

  • 我们将参数的地址转换为强制转换,产生char**类型的值。
  • 我们reinterpret_cast此值为char*
  • 我们取消引用结果,产生char左值。

reinterpret_cast允许您从任何指针类型转换为任何其他指针类型。因此,从char**char*的演员表格格式正确。

答案 2 :(得分:3)

我会尝试使用我对引用和指针的根深蒂固的直觉来解释这一点,而不是依赖于标准的语言。

  • C没有引用类型,它只有值和指针, 因为,在物理上在记忆中,我们只有价值观和指针。
  • 在C ++中,我们添加了对语法的引用,但你可以将它们视为一种语法糖 - 没有特殊的数据结构或内存布局方案来保存引用。

嗯,&#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 ++在你施放时几乎可以提供任何东西。

但至于行为:

  • pc是一个4字节指针
  • (char)pc尝试将指针解释为一个字节,特别是四个字节中的最后一个
  • (char&amp;)pc是相同的,但返回对该字节的引用
  • 当您第一次打印电脑时,没有任何事情发生,您看到存储的信件
  • c ='B'修改4字节指针的最后一个字节,所以它现在指向别的东西
  • 当您再次打印时,您现在指向一个不同的位置来解释您的结果。

由于指针的最后一个字节被修改,新的内存地址就在附近,因此不太可能存在于您的程序不允许访问的内存中。这就是为什么你没有得到seg-fault。获得的实际值是未定义的,但很可能是零,这解释了将其解释为char时的空白输出。