Casting a pointer by reference

1.3k views Asked by At

I came across something I don't understand well. Let's suppose I want to pass a character pointer to a function that takes a reference to a void pointer.

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

I would usually do something like this :

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

Why it is not possible to directly pass the reinterpret_cast to the function?

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;
}

There must be a good reason behind this behavior but I don't see it.

4

There are 4 answers

0
aslg On BEST ANSWER

In the first example, you're actually passing the pointer variable b. So it works.

In the second example, the first reinterpret_cast returns a pointer (by value), which doesn't match the reference the function should get, while the second returns said reference.

As an example to show you how references work, look at these two functions,

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

Say we have this pointer,

unsigned char *a;

Both functions are called the same way,

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

Though it may look like you're passing it by value, but the function takes a reference so it will be passed as a reference.

A reference is similar to a pointer but it has to be initialized with a left value and can't be null.


Having said that, there are much better alternatives to using void* and especially void*&. void* makes code harder to read and easier to shoot yourself in the foot (if anything by making yourself use these strange casts).

As I said in the comments, you could use a template and not bother with void casting.

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

Or,

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

EDIT: On a side note, your second example is missing a semicolon,

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

b is a pointer and doStuff(b) is receiving the address of a pointer. The types match, b is of type void*& (*b is of type void*) and doStuff receives a parameter of type 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;
}

The second call is like the the call from the above function with b as parameter.

The first call is passing simply a void pointer. The types are different, look closer void* is not the same as void*&

1
CinchBlue On

I'm not sure if this is right, but...

I believe it's as simple matching the argument type:

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

You could do the above and the int main() would compile correctly.

A reference is different from a copy of a value--the difference is that the copied value doesn't necessarily need to live in a variable or in a place in memory--a copied value could be just a stack variable while a reference shouldn't be able to point to an expiring value. This becomes important once you start playing around with reference and value semantics.

tl;dr: Don't mix references and values when casting. Doing operations on a reference is different than doing operations on a value; even if argument substitution is implicitly casted.

0
Christopher Oicles On

This is how you would specify a reinterpret_cast as the function argument directly, without using an intermediate variable. As others have told you, it's bad practice, but I want to answer your original question. This is for educational purposes only, of course!

#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";
}

Here we have a pointer to char named ptr and we want to wrangle its type to void*& (a reference to a void pointer), suitable for passing as an argument to function doStuff.

Although references are implemented like pointers, they are semantically more like transparent aliases for another value, so the language doesn't provide the kind of flexibility you get for manipulating pointers.

The trick is: a dereferenced pointer converts directly into a correspondingly typed reference.

So to get a reference to a pointer, we start with a pointer to a pointer:

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

Now the magic of reinterpret_cast brings us closer to our goal:

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

Finally add the dereferencing operator and our masquerade is complete:

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

This compiles fine in Visual Studio 2013. Here is what the program spits out:

Before: '0123456789'
After:  '456789'

The doStuff function successfully advanced ptr by 4 characters, where ptr is a char*, passed by reference as a reinterpret_cast void*.

Obviously, one reason this demonstration works is because doStuff casts the pointer back to a char* to get the updated value. In real-world implementations, all pointers have the same size, so you can probably still get away with this kind of manipulation while switching between types.

But, if you start manipulating pointed-to values using reinterpreted pointers, all kinds of badness can happen. You will also probably be in violation of the "strict aliasing" rule then, so you might as well just change your name to Mister Undefined Behavior and join the circus. Freak.