13

Yesterday I was in class, and at some point the instructor was talking about C code. He said:

What is the purpose of making a pointer cast in C? The only purpose is to make the compiler interpret correctly the pointer operations (for example, adding an int pointer will result in a different offset than adding a char pointer). Apart from that, there is no difference: all pointers are represented the same way in memory, regardless if the pointer is pointing to an int value, a char value, a short value, or whatever. So, casting a pointer will not modify anything in the memory, it will just help the programmer with operations more related with the pointer-type he is dealing with.

However, I have read, specially here in Stack Overflow, that this is not 100% true. I have read that in some weird machines, pointers for different types can be stored in different ways in memory. In this case, not changing the pointer to the correct type could cause problems if the code is compiled to this kind of machine.

Basically, this is the kind of code I'm talking about. Consider the code below:

int* int_pointer;
char* char_pointer;
int_pointer = malloc(sizeof(int));
*int_pointer = 4;

And now two options:

1.

char_pointer = (char *)int_pointer;

2.

char_pointer = int_pointer;

The code on case 2 could became a problem? Making the cast (case 1) would eventually change the pointer format in memory (if yes, could you give an example of machine?)?

Thanks

felipeek
  • 1,193
  • 2
  • 10
  • 31
  • 4
    "Apart from that, there is no difference: all pointers are represented the same way in memory, regardless if the pointer is pointing to an int value, a char value, a short value, or whatever." – that's **false,** i. e. factually wrong in the general case, and if your teacher is teaching this, he should be fired. There's a good reason the second snippet should give you at least a warning but if I recall correctly, it's a constraint violation, so it triggers undefined behavior instantly. As such, it can very realistically lead to hard-to-debug errors when a compiler exploits the UB in your… – The Paramagnetic Croissant May 13 '15 at 20:13
  • …code (and boy, do they do that cleverly!) – The Paramagnetic Croissant May 13 '15 at 20:13
  • Has to do with the size of a `char` and `int` on different machines and also memory alignment. None of these are standard ( I think) – smac89 May 13 '15 at 20:13
  • @Smac89 (in that the number of bits is not exactly specified, but `sizeof(char)` must always be 1 on a conforming implementation.) – The Paramagnetic Croissant May 13 '15 at 20:14
  • 1
    Of course it is important to cast. You will realize the importance as soon as you de-reference the pointer. – SandBag_1996 May 13 '15 at 20:14
  • 1
    If in doubt, omit all pointer casts, and enable all compiler warnings. Then follow up the implications. – Weather Vane May 13 '15 at 20:23
  • 1
    "casting a pointer will not modify anything in the memory" it usually true on many platforms but not true in general. – chux - Reinstate Monica May 13 '15 at 20:32
  • 3
    First of all you should ask yourself the question, why the hell would I try to convert or cast one pointer type to another. In most of the cases that I see for that, this is just crappy code, and usually has a lot of other problems such as alignment and aliasing. Simply don't do that, you don't need that, and then you also don't need casts. **Most casts are evil.** The only pointer conversion that usually make sense are from and to `void*`. – Jens Gustedt May 13 '15 at 21:07

6 Answers6

14

Not casting pointers in C can cause problems?

There is no implicit conversion between pointer types in C (as there are for example between arithmetic types) - with the exception with void * type. It means C requires you to cast if you want to convert a pointer to another pointer type (except for void *). If you fail to do so an implementation is required to issue a diagnostic message and is allowed to fail the translation.

Some compilers are nice enough (or pervert enough) to not require the cast. They usually behave as if you explicitly put the cast.

 char *p = NULL;
 int *q =  NULL;

 p = q;  // not valid in C

In summary you should always put the cast when converting pointers to pointers for portability reasons.

EDIT:

Outside portability, an example where not casting can cause you real problems is for example with variadic functions. Let's assume an implementation where the size of char * is larger than the size of int *. Let's say the function expects the type of one argument to be char *. If you want to pass an argument of int *, you then have to cast it to char *. If you don't cast it, when the function will access the char * object some bits of the object will have an indeterminate value and the bevahior will be undefined.

A somewhat close example is with printf, if a user fails to cast to void * the argument for the p conversion specifier, C says it invokes undefined behavior.

ouah
  • 142,963
  • 15
  • 272
  • 331
  • @ouah Thanks for your answer! It was VERY helpful. Unfortunately I can only accept one answer as the correct answer – felipeek May 15 '15 at 00:11
11

What your instructor said about all pointer types sharing the same representation is generally true for real-life implementations of C language.

However, it is not true from the point of view of abstract C language itself. C language guarantees that

  1. char * pointers are represented in the same way as void * pointers.
  2. Pointers to a qualified version of type T (const, volatile, restrict) are represented in the same way as pointers to unqualified T.
  3. Pointers to all struct types are represented identically.
  4. Pointers to all union types are represented identically.

That is all. No other guarantees exist. Which means that as far as the language itself is concerned, int * pointer has different representation from double * pointer. And pointer to struct S is represented differently from pointer to union U.

However, your example with char_pointer and int_pointer, as presented, is not exactly illustrative. The char_pointer = int_pointer; assignment is simply invalid (as in "does not compile"). Language does not support implicit conversions between incompatible pointer types. Such conversions always require an explicit cast operator.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • An interesting thing is `malloc`, for which C standard make explicit effort to make casting its return value safe and portable, like forcing pointer returned by `malloc` be maximum-naturally-aligned. – user3528438 May 13 '15 at 21:11
  • Er. Doesn't it also guarantee that `void *` can represent any pointer to type other than function pointers? I'd have sworn that it did. But then I've been wrong before. – dmckee --- ex-moderator kitten May 14 '15 at 01:58
  • 2
    @dmckee: Yes, `void *` *can* represent any data pointer value, but that simply means that any data pointer can be converted to `void *` without any loss of information. I.e. you can convert any data pointer `T *` to `void *` and then convert it back to `T *` and obtain the original `T *` value. This is guaranteed by the language. Note that for that `void *` does not have to use the same representation in memory as all other data pointer types. It can have a completely different representation, as long as that representation guarantees that pointer value is preserved. – AnT stands with Russia May 14 '15 at 03:53
  • 2
    @dmckee: Meanwhile, the OP's quote makes a much stronger assertion: *"all pointers are represented the same way in memory"*. This is a completely different story. And this is not true. The OP's quote claims that, among other things, a conversion between pointer types is purely conceptual, it is a no-op in the generated code. It is indeed so in most practical cases, but the formal language definition says that converting some `T *` to `void *` might involve some actual "work". – AnT stands with Russia May 14 '15 at 03:54
  • 1
    @dmckee: And converting `void *` back to `T *` might involve some actual "work". This is OK, as long as such round-trip conversion preserves the original `T* ` value. – AnT stands with Russia May 14 '15 at 03:57
  • @AnT Thanks for your answer and comments. It was VERY helpful. One point about your comments I'm not sure yet: The difference between a void pointer and any other pointer is just that *the language guarantees that the programmer can convert from one pointer to another without any loss of information?* I mean, this is the reason why the void pointer is "special"? If so, could you give an example when converting one non-void pointer to another non-void pointer could become a problem? For example, converting one int pointer to a double pointer and then converting it back could cause problems? – felipeek May 15 '15 at 00:10
  • 1
    @felipeek: Precisely. The language spec says that round-trip conversion `T * -> U * -> T *` is possible only if the original `T *` pointer value is also properly aligned for the `U *` type. Obviously, if `U`'s alignment requirements are stricter than `T`'s you can easily violate that requirement . This causes undefined behavior at `T * -> U *` stage already (per C99). – AnT stands with Russia May 15 '15 at 02:38
  • 1
    @felipeek: C++ is more permissive in that regard (at least in C++03). It says that converting to a type with the same or looser alignment requirements and then converting back produces the original pointer value. But converting to a type with *stricter* alignment requirements might "ruin" the pointer value irreversibly (the result is formally unspecified). This means that `int * -> double * -> int *` conversion might not produce the original value, if `double` has stricter alignment requirements than `int`. – AnT stands with Russia May 15 '15 at 02:42
3

What your Prof said sounds right.

...(for example, adding an int pointer will result in a different offset than adding a char pointer)

This is true because assuming int is 4 bytes and char is 1 byte, adding a 2 to an int pointer will result in moving the pointer by 8 bytes, whereas adding a 2 to a char pointer will only move the pointer to the next byte 2 positions away.

Apart from that, there is no difference: all pointers are represented the same way in memory, regardless if the pointer is pointing to an int value, a char value, a short value, or whatever.

This is true, the machine makes no distinction between char vs int pointers, these are the compiler's issue to deal with. Also as your prof mentioned:

The only purpose is to make the compiler interpret correctly the pointer operations

So, casting a pointer will not modify anything in the memory, it will just help the programmer with operations more related with the pointer-type he is dealing with.

This is again true. Casting does not modify memory, it just changes how a part of memory is interpreted.

smac89
  • 39,374
  • 15
  • 132
  • 179
2

Casting a pointer doesn't change the pointer value, it only tells the compiler what to do with the pointer. The pointer itself is just a number, and it doesn't have a type. It's the variable where the pointer is stored that has the type.

There is no difference between your two ways of assigning the pointer (if the compiler lets you do the assigment). Whatever the type of the source pointer variable, the pointer will be used according to the type of the destination variable after the assignment.

You simply can't store an int pointer in a char pointer variable. Once the value is stored in the variable, it has no notion of having been a pointer to an int.

Casting pointers matters when you use the value directly, for example:

int* int_pointer;
int_pointer = malloc(sizeof(int));
*int_pointer = 4;
char c;
c = *(char*)int_pointer;

Casting the pointer means that dereferencing it reads a char from the location, not an int.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    "Casting a pointer doesn't change the pointer value" – this is only true if by this you mean "so it compares equal to the original pointer when converted back to its original type". Otherwise, the specific bitwise representation of the result of converting a pointer to an incompatible type is not required by the Standard. – The Paramagnetic Croissant May 13 '15 at 20:30
  • There is also no guarantee what happens if a pointer is not properly aligned. For example char* p = malloc (100); int* q = (int *) (p + 1); anything could happen. – gnasher729 May 13 '15 at 21:58
2

Casting from void* to a type i need in my program (e.g. int*, char*, struct x*) makes sense to me. But casting across other types i.e. int* to char* etc. can become problematic (due to the offsets being different for ints, chars etc). So be careful if doing so.

But ... to specifically answer your question, consider the following:

int_pointer = malloc(2 * sizeof(int)); // 2 * 4 bytes on my system.
*int_pointer = 44;
*(int_pointer + 1 )= 55;

printf("Value at int_pointer = %d\n", *int_pointer); // 44
printf("Value at int_pointer + 1 = %d\n", *(int_pointer + 1)); // 55

(The offsets above will be in 4 byte increments.)

//char_pointer = int_pointer; // Gives a warning ...
char_pointer = (char*)int_pointer; // No warning.
printf("Value at char_pointer = %d\n", *char_pointer); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+1)); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+2)); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+3)); // 44 <==

Four bytes were allocated to the int value of 44. Binary representation of 44 is 00101100 ... the remaining 3 bytes are all 0s. So when accessing using char* pointer, we increment 1 byte at a time and get the above values.

iammowgli
  • 159
  • 4
1

Casting for pointers is essential to say the compiler how to perform pointer arithmetic.

For example if x is a character pointer pointing to memory 2001, x++ should go to 2002, but if x is an integer pointer x++ should be 2003

so if type casting is not done for pointers compiler won't be able to perform pointer arithematic properly.

Ankit Zalani
  • 3,068
  • 5
  • 27
  • 47