Cody Cody - 27 days ago 4
C Question

Is there a reason to do a cast like this?

I'm working with this large open source C library, and I frequently find casts between types that look like this:

char *str;
//some code
unsigned char *str2 = *(unsigned char **) &str;

I played around with it, and when I changed it to look like

unsigned char *str2 = (unsigned char *) str;

It appeared to work without issues. Note that these casts are frequent throughout the code, used on many types other than unsigned char.

Is there a reason to cast like this by referencing and dereferencing?

Edit: I don't know if it is relevant, but this code is supposed to be C89 compliant.

Edit 2: Some specific examples

void *q = *(void **)(&p[i]);

found at: memento.c (line 1122)

unsigned int rgba = *((unsigned int *)color);

found at: draw-paint.c (line 332)

return cbz_strnatcmp(*(const char **)a, *(const char **)b);
//both parameters are expected to be const char*

found at: mucbz.c (line 73)


Each of the two alternatives presented involves converting a pointer of one type to a different pointer type via a cast. This is permitted, including in C89. In the original code, the converted pointer is explicitly dereferenced; in the revised code it is to be assumed that the pointer will be dereferenced elsewhere. In these regards, the two variations perform exactly the same kinds of (allowed) behavior.

There is one technical difference, however: the first alternative causes a value of type char * to be accessed via an lvalue of type unsigned char * when the value of the initialization expression is read so as to be assigned to str2. Those two are not "compatible types" in the standard's sense of that term, nor is the latter an unsigned type corresponding to the former (pointers themselves don't have signedness), nor is the latter a character type or differently-qualifed version of the former, or a union type. The access therefore violates the provision of the standard colloquially known as the "strict aliasing rule".

The second alternative also converts between incompatible pointer types, but the subsequent accesses it provides for (and that the first alternative also provides for) are allowed by the strict aliasing rule, both because character types can alias anything, and because unsigned char is the unsigned type corresponding to char, and aliasing between such corresponding types is permitted.

In practice, it's unlikely that any production-ready compiler would do anything with the original code other than what is apparently expected, but your revised code is both cleaner and more correct.