-5

I'm testing an issue that I'm having when casting void pointers to a different type within the same variable. The issue seems to be when trying to use the same variable for the casted pointer. Below I have a section of code that tests this. Would someone be able to tell me why the program fails to compile for that case?

#include <stdlib.h>

typedef struct test {

  int a;

}test;

int main() {

  void * check;
  test * check2;
  check =  malloc(sizeof(test));

  check = ((test *) check);
  check2 = (test *)check;


  check->a = 2; //this raises an error (dereferencing void pointer) when compiling
  check2->a = 2; //this works
  ((test *)check)->a = 2; //this also works
}
user0042
  • 7,917
  • 3
  • 24
  • 39
Kumar
  • 5
  • 3
  • 2
    You cannot dereference (i.e. apply operator `*` or operator `->` to) a void pointer. –  Sep 05 '17 at 23:54
  • 2
    Do you think that `check = ((test *) check);` changes the _type_ of `check`? – Mooing Duck Sep 05 '17 at 23:54
  • @MooingDuck Why would that not change the type of check? – Kumar Sep 06 '17 at 00:02
  • 2
    Because the type of an object is determined by its declaration, not by any value that might be assigned to it later. This: `check = ((test *) check);` explicitly converts the value of `check` from `void*` to `test*`, and then implicitly, via the assignment, converts the result right back to `void*`. Similarly, `int n; n = 2.5;` doesn't result in `n` being able to hold fractional values; it's still an `int`. – Keith Thompson Sep 06 '17 at 00:07
  • `check = ((test *) check);` does nothing. Your compiler probably will optimize it away – pm100 Sep 06 '17 at 00:11
  • Changing types is not possible at all. – user7860670 Sep 06 '17 at 06:48

1 Answers1

0

Casting a pointer is just lying to your compiler

void * ptr = malloc(sizeof(test));
((test *)ptr)->a = 5;

In the second line, we are telling the compiler "I know I declared ptr as a (void *), but I'm smarter than you, and trust me, it's actually a (test *)". Nothing has changed, ptr is still just a pointer, an address of a memory location, but the compiler assumes it's pointing to something specific.

  check->a = 2; //this raises an error (dereferencing void pointer) when compiling

You must cast your variables every single time you want the compiler to treat it as something different from what you declared it to be.

A more interesting scenario to explain what you can do with casting pointers...

// struct with two ints, a and b
typedew struct testAB {
  int a;
  int b;
} testAB;

// struct with two ints, named in reverse order
typedef struct testBA {
  int b;
  int a;
} testBA;

int main(void) {
  // construct a testAB struct
  void *ptr = malloc(sizeof(testAB));
  ((testAB *)ptr)->a = 0
  ((testAB *)ptr)->b = 1

  // treat ptr as a testBA struct
  if ( ((testBA *)ptr)->a == 1) {
    printf("this shouldn't happen, 'a' was set to 0");
  }
}

if you run the above code, you will find that the printf statement will be executed. Even though we set 'a' to be 0, and the if statement checks that a == 1.

This is because structs are stupidly simple. In the above example the struct(s) is just two int's packed next to eachother. Much like an array of two ints, like this:

int array[2] = {0, 1};
void *ptr = &array; 

Even with this representation, we can lie to the compiler, we can force the compiler to treat this 'array' as one of our structs.

if ( ((testAB *)array)->a == 0 )
  printf("a is 0");

This is because, under the hood, the compiler treats named variables in a struct as just offests from where the struct is.

// the following two lines have the same affect
((testAB *)array)->a = 0;
array[0] = 0;

// the following two lines have the same affect
((testAB *)array)->b = 2;
array[1] = 2;

if we tell the compiler that it's a (testAB *), then 'a' means the first int, and 'b' means the second. If we tell the compiler that it's a (testBA *), then 'a' is the second, and 'b' is the first.

In compiled code ALL variable names are lost. The compiler reduces the struct assignment, down to "set the second int of the struct to 2". Or more specifically, if we are dealing with 32-bit ints, set bytes 5, 6, 7 and 8 of the struct to 0000, 0000 0000 0010 (in binary. (or perhaps in reverse order, if we are compiling for a little endian CPU)

Reuben Crimp
  • 311
  • 4
  • 12