按引用强制转换指针

Casting a pointer by reference

本文关键字:转换 指针 引用      更新时间:2023-10-16

我遇到了一些不太明白的东西。假设我想将字符指针传递给一个函数,该函数引用了一个 void 指针。

void doStuff(void*& buffer)
{
  // do something
}

我通常会做这样的事情:

int main()
{
  unsigned char* buffer = 0;
  void* b = reinterpret_cast<void *>(buffer);
  doStuff(b);
  return 0;
}

为什么不能直接将reinterpret_cast传递给函数?

int main()
{
  unsigned char* buffer = 0
  // This generate a compilation error.
  doStuff(reinterpret_cast<void *>(buffer));
  // This would be fine.
  doStuff(reinterpret_cast<void *&>(buffer));
  return 0;
}

这种行为背后一定有一个很好的理由,但我看不出来。

在第一个示例中,您实际上是在传递指针变量 b。所以它有效。

在第二个示例中,第一个reinterpret_cast返回一个指针(按值),该指针与函数应获取的引用不匹配,而第二个返回所述引用。

作为向您展示引用如何工作的示例,请查看这两个函数,

void doSomething( unsigned char *ptr );
void doSomethingRef( unsigned char *&ptr );

假设我们有这个指针,

unsigned char *a;

这两个函数的调用方式相同,

doSomething( a ); // Passing pointer a by value
doSomethingRef( a );// Passing pointer a by reference

虽然看起来您是通过值传递它,但该函数接受引用,因此它将作为引用传递。

引用类似于指针,但它必须使用 left 值初始化,并且不能为 null。


话虽如此,使用void*有更好的选择,尤其是void*&.void*使代码更难阅读,更容易搬起石头砸自己的脚(如果有的话,让自己使用这些奇怪的强制)。

正如我在评论中所说,您可以使用模板而不必为空隙铸造而烦恼。

template< class T > void doStuff( T *&buffer ) {
    ...
}

template< class T > T* doStuff( T* buffer ) {
    ...
}

编辑:附带说明一下,您的第二个示例缺少分号,

unsigned char* buffer = 0; // Right here
int main()
{
  unsigned char* buffer = 0;
  void* b = reinterpret_cast<void *>(buffer);
  doStuff(b);
  return 0;
}

b 是一个指针,doStuff(b)接收指针的地址。类型匹配,bvoid*& 类型(*bvoid* 类型),doStuff 接收一个类型为 void*& 的参数。


int main()
{
  unsigned char* buffer = 0
  // This generate a compilation error.
  doStuff(reinterpret_cast<void *>(buffer));
  // This would be fine.
  doStuff(reinterpret_cast<void *&>(buffer));
  return 0;
}

第二个调用类似于上述函数的调用,以 b 作为参数。

第一个调用只是传递一个void指针。类型不同,仔细看void*void*&

不一样

这是直接指定reinterpret_cast作为函数参数的方式,而无需使用中间变量。 正如其他人告诉你的那样,这是不好的做法,但我想回答你最初的问题。 当然,这只是出于教育目的!

#include <iostream>
void doStuff(void*& buffer) {
    static const int count = 4;
    buffer = static_cast<void*>(static_cast<char*>(buffer) + count);
}
int main() {
    char str[] = "0123456789";
    char* ptr = str;
    std::cout << "Before: '" << ptr << "'n";
    doStuff(*reinterpret_cast<void**>(&ptr));   // <== Here's the Magic!
    std::cout << "After:  '" << ptr << "'n";
}

在这里,我们有一个名为ptr的char指针,我们希望将其类型转换为void*&(对void指针的引用),适合作为参数传递给函数doStuff。

尽管引用的实现方式类似于指针,但它们在语义上更像是另一个值的透明别名,因此该语言无法提供操作指针的灵活性。

诀窍是:取消引用的指针直接转换为相应的类型引用。

因此,为了获取对指针的引用,我们从指向指针的指针开始:

&ptr  (char** - a pointer to a pointer to char)

现在,reinterpret_cast的魔力使我们更接近我们的目标:

reinterpret_cast<void**>(&ptr)  (now void** - a pointer to a void pointer)

最后添加取消引用运算符,我们的伪装就完成了:

*reinterpret_cast<void**>(&ptr)   (void*& - a reference to a void pointer)

这在Visual Studio 2013中编译得很好。 以下是程序吐出的内容:

Before: '0123456789'
After:  '456789'

doStuff 函数成功地将 ptr 推进了 4 个字符,其中 ptr 是一个字符*,通过引用作为 reinterpret_cast void* 传递。

显然,这个演示工作的一个原因是因为doStuff将指针转换回char*以获取更新的值。 在实际实现中,所有指针都具有相同的大小,因此在类型之间切换时,您可能仍然可以摆脱这种操作。

但是,如果您开始使用重新解释的指针来操作指向的值,则可能会发生各种不良情况。 那时你也可能违反"严格混叠"规则,所以你不妨把你的名字改成未定义的行为先生,加入马戏团。 怪物。

我不确定这是否正确,但是...

我相信匹配参数类型很简单:

void doStuff(void* buffer) {
    std::cout << reinterpret_cast<char*>(buffer) << std::endl;
    return;
}

您可以执行上述操作,int main()将正确编译。

引用不同于值的副本 - 不同之处在于复制的值不一定需要存在于变量中或内存中的某个位置 - 复制的值可能只是一个堆栈变量,而引用不应该指向即将过期的值。一旦你开始玩弄引用和值语义,这一点就变得很重要。

tl;dr:投射时不要混合引用和值。对引用执行操作不同于对值执行操作;即使参数替换是隐式转换的。