Proper Way of Casting Pointer Types

Proper way of casting pointer types

For convertible pointers to fundamental types both casts have the same meaning; so you are correct that static_cast is okay.

When converting between some pointer types, it's possible that the specific memory address held in the pointer needs to change.

That's where the two casts differ. static_cast will make the appropriate adjustment. reinterpret_cast will not.

For that reason, it's a good general rule to static_cast between pointer types unless you know that reinterpret_cast is desired.

Type conversion and type casting of pointers in C++

It's important to understand that, as far as the C++ language is concerned, the distinction you're talking about does not exist. All "*_casts" (and their C equivalents) are all just (explicit) conversions. And for the language, all conversions create a new object (or a new reference to an existing object if the conversion is to a reference type).

Pointers are objects. int* p2 = (int*) p1; performs a reinterpret_cast, which creates a new pointer object of a different type. p2 is a distinct object from p1, even though they are pointing to the same memory. If you did ++p2, that would in no way affect what p1 points to.

So from the perspective of the language, a cast is just certain explicit conversions that use syntax that has the word "cast" in it (or syntax equivalent to syntax with the word "cast" in it).

The distinction you're trying to make is basically when a conversion returns a value that is binary identical to the original object and when it doesn't. Well, C++ doesn't really care; all (non-reference) conversions return a new object. C++ neither has nor needs terminology to describe conversions when the new object is binary-identical to the old. And there are plenty of "*_cast" operations which may not return binary-identical values, so even if one were to desire to create such a concept, calling it a "cast" would be confusing.

Also why is a conversion of user-defined class pointer called a standard conversion?

Because that's what the C++ standard calls them. I don't mean that in the sense that we call them "standard" because they're in the C++ standard. I mean the C++ standard has a concept called "standard conversion", and conversions between certain kinds of pointers to classes are part of that.

A "standard conversion" is a conversion that can happen implicitly (ie: without specialized syntax at the site of the conversion). A pointer to a derived class can be implicitly converted to a pointer to an accessible base class. This is part of what polymorphic inheritance is for: the ability for some code to act on a derived class instance without knowing exactly which type it is acting on. The polymorphic base class provides a general interface which code is written to use, and the derived classes provide various implementations of that interface.

Typecasting of pointers in C

A program well written usually does not use much pointer typecasting. There could be a need to use ptr typecast for malloc for instance (declared (void *)malloc(...)), but it is not even necessary in C (while a few compilers may complain).

  int *p = malloc(sizeof(int)); // no need of (int *)malloc(...)

However in system applications, sometimes you want to use a trick to perform binary or specific operation - and C, a language close to the machine structure, is convenient for that. For instance say you want to analyze the binary structure of a double (that follows thee IEEE 754 implementation), and working with binary elements is simpler, you may declare

  typedef unsigned char byte;
double d = 0.9;
byte *p = (byte *)&d;
int i;
for (i=0 ; i<sizeof(double) ; i++) { ... work with b ... }

You may also use an union, this is an exemple.

A more complex utilisation could be the simulation of the C++ polymorphism, that requires to store the "classes" (structures) hierarchy somewhere to remember what is what, and perform pointer typecasting to have, for instance, a parent "class" pointer variable to point at some time to a derived class (see the C++ link also)

  CRectangle rect;
CPolygon *p = (CPolygon *)▭
p->whatami = POLY_RECTANGLE; // a way to simulate polymorphism ...
process_poly ( p );

But in this case, maybe it's better to directly use C++!

Pointer typecast is to be used carefully for well determined situations that are part of the program analysis - before development starts.

Pointer typecast potential dangers

  • use them when it's not necessary - that is error prone and complexifies the program
  • pointing to an object of different size that may lead to an access overflow, wrong result...
  • pointer to two different structures like s1 *p = (s1 *)&s2; : relying on their size and alignment may lead to an error

(But to be fair, a skilled C programmer wouldn't commit the above mistakes...)

Best practice

  • use them only if you do need them, and comment the part well that explains why it is necessary
  • know what you are doing - again a skilled programmer may use tons of pointer typecasts without fail, i.e. don't try and see, it may work on such system / version / OS, and may not work on another one

Simple c++ pointer casting

static_cast<void*> annihilate the purpose of type checking as you say that now it points on "something you don't know the type of". Then the compiler have to trust you and when you say static_cast<unsigned char*> on your new void* then he'll just try to do his job as you ask explicitely.

You'd better use reinterpret_cast<> if you really must use a cast here (as it's obvioulsy showing a design problem here).

How to cast a pointer to char to a pointer to int

I want to put the back and forth under @Bathsheba's post to rest. So here's an answer about the finer details of what you are doing.


@Sean already suggested you reinterpret_cast your pointers instead. And that is equivalent to your second chain of casts. It says as much in [expr.reinterpret.cast]/7:

An object pointer can be explicitly converted to an object pointer of
a different type. When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_­cast<cv T*>(static_­cast<cv void*>(v)). [ Note: Converting a
prvalue of type “pointer to T1” to the type “pointer to T2” (where T1
and T2 are object types and where the alignment requirements of T2 are
no stricter than those of T1) and back to its original type yields the
original pointer value.  — end note ]

Now, let's examine each step of the two step conversion. First we have a static_cast<void*>. According to [conv.ptr]/2 (emphasis mine):

A prvalue of type “pointer to cv T”, where T is an object type, can be
converted to a prvalue of type “pointer to cv void”. The pointer value
is unchanged by this conversion
.

The first conversion doesn't do any alteration to the address. And then it also says in [basic.compound]/5:

A pointer to cv-qualified or cv-unqualified void can be used to point
to objects of unknown type. Such a pointer shall be able to hold any
object pointer. An object of type cv void* shall have the same
representation and alignment requirements as cv char*.

So a char* may store any address a void* may store. Now it doesn't mean the conversion from void* to char* is value preserving, only that they can represent the same values. Now, assuming a very restricted use case, that is enough of a guarantee. But there's more at [expr.static.cast]/13:

A prvalue of type “pointer to cv1 void” can be converted to a prvalue
of type “pointer to cv2 T”, where T is an object type and cv2 is the
same cv-qualification as, or greater cv-qualification than, cv1. If
the original pointer value represents the address A of a byte in
memory and A does not satisfy the alignment requirement of T, then the
resulting pointer value is unspecified. Otherwise, if the original
pointer value points to an object a, and there is an object b of type
T (ignoring cv-qualification) that is pointer-interconvertible with a,
the result is a pointer to b. Otherwise, the pointer value is
unchanged by the conversion
.

Where am I going with this? Assuming pc already holds the address of an int (suitably converted according to the above), then casting the char* to an int* via reinterpret_cast will give you the address of the original int. The note under the first paragraph says as much, and the further quotes prove it. If it doesn't hold the address of an int, you are playing roulette and are likely going to lose. Your program has undefined behavior. You should follow Bathsheba's advice to the letter.

C++ type casting with pointers

In both your examples you're making mistakes making the code not compile. So I'll assume you're trying to do the following:

int x = 1;
char c = *((char*)&x);

Depending on your architecture, c will now have either the value of the least or the most significant byte of x. In this example this would be either 0 or 1 (this can actually be used to detect the byte ordering).

Your second example won't work, cause you're trying to ignore the const resulting in an illegal operation/bad cast (this is also called "const correctness").

Edit: Regarding your comment about "what does it mean?":

In expressions:
&somevariable will return the address of somevariable.
*somevariable will assume the contents of somevariable are the address of the actual value, which is then returned.

In declarations:
datatype is a normal variable/object. This is passed "by value".
datatype& is a reference. This works exactly like normal variables in Java/C# and is passed by reference.
datatype* is a pointer. This just contains the address where the actual value is located (see above) and is essentially passed by reference as well.

Actual casts work pretty much similar to Java/C#, but pointers are just that: They point to the location of the actual value. While this might confuse you, pointers in C/C++ work pretty much like the standard variables/references used in Java/C#.

Look at this:

MyClass x; // object of MyClass
MyClass *x; // pointer to an object of MyClass - the actual value is undefined and trying to access it will most likely result in an access violation (due to reading somewhere random).
MyClass *x = 0; // same as above, but now the default value is defined and you're able to detect whether it's been set (accessing it would essentially be a "null reference exception"; but it's actually a null pointer).
MyClass &x = MyClass(); // creating a new reference pointing to an existing object. This would be Java's "MyClass x = new MyClass();"

Cast pointer type in C

Your code has Undefined Behaviour. Therefore anything could happen.

The UB is because you are casting a char which is one byte to types that are 4 and 8 bytes, which means you are (potentially) accessing memory out of bounds, or with the wrong alignment.

Whether any of this will "work" or "not work" on any particular system is not very relevant, because the code is erroneous.



Related Topics



Leave a reply



Submit