Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why strange behavior with casting back pointer to the original class?

Assume that in my code I have to store a void* as data member and typecast it back to the original class pointer when needed. To test its reliability, I wrote a test program (linux ubuntu 4.4.1 g++ -04 -Wall) and I was shocked to see the behavior.

struct A
{
  int i;
  static int c;
  A () : i(c++) { cout<<"A() : i("<<i<<")\n"; }
};
int A::c;

int main ()
{
  void *p = new A[3];  // good behavior for A* p = new A[3];
  cout<<"p->i = "<<((A*)p)->i<<endl;
  ((A*&)p)++;
  cout<<"p->i = "<<((A*)p)->i<<endl;
  ((A*&)p)++;
  cout<<"p->i = "<<((A*)p)->i<<endl;
}

This is just a test program; in actual for my case, it's mandatory to store any pointer as void* and then cast it back to the actual pointer (with help of template). So let's not worry about that part. The output of the above code is,

p->i = 0
p->i = 0 // ?? why not 1
p->i = 1

However if you change the void* p; to A* p; it gives expected behavior. WHY ?

Another question, I cannot get away with (A*&) otherwise I cannot use operator ++; but it also gives warning as, dereferencing type-punned pointer will break strict-aliasing rules. Is there any decent way to overcome warning ?

like image 367
iammilind Avatar asked Jun 11 '11 04:06

iammilind


People also ask

Can you cast a function pointer?

Yes, it can. This is purpose of casting function pointers, just like usual pointers. We can cast a function pointer to another function pointer type but cannot call a function using casted pointer if the function pointer is not compatible with the function to be called.

Why do we use function pointers?

In C, we can use function pointers to avoid code redundancy. For example a simple qsort() function can be used to sort arrays in ascending order or descending or by any other order in case of array of structures. Not only this, with function pointers and void pointers, it is possible to use qsort for any data type.

What is the use of function pointer in C?

Function pointers in C can be used to create function calls to which they point. This allows programmers to pass them to functions as arguments. Such functions passed as an argument to other functions are also called callback functions.


2 Answers

Well, as the compiler warns you, you are violating the strict aliasing rule, which formally means that the results are undefined.

You can eliminate the strict aliasing violation by using a function template for the increment:

template<typename T>
void advance_pointer_as(void*& p, int n = 1) {
    T* p_a(static_cast<T*>(p));
    p_a += n;
    p = p_a;
}

With this function template, the following definition of main() yields the expected results on the Ideone compiler (and emits no warnings):

int main()
{
    void* p = new A[3];
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
    advance_pointer_as<A>(p);
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
    advance_pointer_as<A>(p);
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
}
like image 152
James McNellis Avatar answered Sep 18 '22 23:09

James McNellis


You have already received the correct answer and it is indeed the violation of the strict aliasing rule that leads to the unpredictable behavior of the code. I'd just note that the title of your question makes reference to "casting back pointer to the original class". In reality your code does not have anything to do with casting anything "back". Your code performs reinterpretation of raw memory content occupied by a void * pointer as a A * pointer. This is not "casting back". This is reinterpretation. Not even remotely the same thing.

A good way to illustrate the difference would be to use and int and float example. A float value declared and initialized as

float f = 2.0;

cab be cast (explicitly or implicitly converted) to int type

int i = (int) f;

with the expected result

assert(i == 2);

This is indeed a cast (a conversion).

Alternatively, the same float value can be also reinterpreted as an int value

int i = (int &) f;

However, in this case the value of i will be totally meaningless and generally unpredictable. I hope it is easy to see the difference between a conversion and a memory reinterpretation from these examples.

Reinterpretation is exactly what you are doing in your code. The (A *&) p expression is nothing else than a reinterpretation of raw memory occupied by pointer void *p as pointer of type A *. The language does not guarantee that these two pointer types have the same representation and even the same size. So, expecting the predictable behavior from your code is like expecting the above (int &) f expression to evaluate to 2.

The proper way to really "cast back" your void * pointer would be to do (A *) p, not (A *&) p. The result of (A *) p would indeed be the original pointer value, that can be safely manipulated by pointer arithmetic. The only proper way to obtain the original value as an lvalue would be to use an additional variable

A *pa = (A *) p;
...
pa++;
...

And there's no legal way to create an lvalue "in place", as you attempted to by your (A *&) p cast. The behavior of your code is an illustration of that.

like image 34
AnT Avatar answered Sep 19 '22 23:09

AnT