繁体   English   中英

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

[英]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)

在我们的例子中, Uchartchar*

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

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

reinterpret_cast允许您从任何指针类型转换为任何其他指针类型。 因此,从char**char*的演员char**很好。

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

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

那么,从这个角度来看,什么是“参考”? 或者更确切地说,您将如何“实施”参考? 当然,用指针。 因此,无论何时在某些代码中看到引用,您都可以假装它只是一个以特殊方式使用的指针: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 ++在你投射时几乎可以提供任何东西。

但至于行为:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM