Fundamental C - Pointers, Cast & Type Punning
Written by Harry Fairhead   
Monday, 10 September 2018
Article Index
Fundamental C - Pointers, Cast & Type Punning
Widening & Narrowing Casts
Type Punning
Undefined Behaviour

Things are a little different if you try a cast between types that have different memory allocation sizes.

If the assignment is to a larger memory allocation, a widening assignment then everything should work as the low order bytes of the source are transferred to the low order bytes of the destination and the higher ordered bytes in the destination are simply set to zero or one.

For example:

char myChar;
myChar='A';
int myVar1 = (int) myChar;

sets myVar1 to 65 i.e. all bytes zero apart from the lowest order byte which is set to 01000001.

Now compare this to:

myChar=-1;
int myVar1 = (int) myChar;

In this case the bit pattern in myChar is 11111111 and this is transferred to the low order byte of myVar1, the remaining bytes are all set to 1.

This might sound like a complicated way of doing things but it ensures that the signed value is the same as in the narrower variable.

For unsigned widening the high bytes in the wider variable are just set to zero again to preserve the interpretation of the value.

An assignment to a narrower type also follows the same pattern of trying to preserver the value of the bit pattern but this time it might not be possible. What happens is that the wider type is truncated to the smaller size. If the value still represents the original value then so much to the good if it doesn’t then you don’t get any warnings.

Put simply if the wider type is storing a value that is representable by the narrower type then copying it just the lower bytes transfers the value unchanged for signed and unsigned values.

Notice that when the value in the wider type cannot be represented by the narrower type what you get depends on the representation used by the machine for negative value.

For example:

char myChar;
int myVar=1024;
myChar= (char) myVar;
printf("%d \n",myChar)

in this case myVar stores 1024 which cannot be represented in a single byte and so the assignement works but does not preserve the value. The lower byte, which is zero, it transferred to myChar and so we see zero printed.

Now consider:

char myChar;
int myVar=-1024;
myChar= (char) myVar;
printf("%d \n",myChar);

this two prints zero because the values stored in myVar cannot be represented in a single byte and the lower byte in myVar is zero but if ones complement was used for negative values the result would be one.

The general principle for casting integer types in assignments or more generally in expression is – transfer as many bytes in the source as the destination can hold and if the source is signed set the unused bytes to zero for a positive value and to one for a negative value.

Casting & Pointers

Now we come to a very important topic – casting pointers.

A pointer is an address of a block of memory and you can either regard that block of memory as typed or untyped. The difference comes down to the type of the pointer. If you recall the type of a pointer modifies how pointer arithmetic is done and how the compiler treats what the pointer references.

A raw block of memory that is just treated as such, a block of bytes if you like, needs an untyped pointer. This is declared as a pointer to void:

void *myPointer; 

As mentioned earlier C tends to use the term void for anything that isn’t something. A void pointer can point at anything and the compiler will not assume anything about it and hence pointer arithmetic on a void pointer isn’t allowed. Note: some compilers do allow, or can be set to allow void pointer arithmetic.

If you want to treat a block of memory as a set of bytes and do pointer arithmetic then the simplest thing to do is to use a pointer to char.

When you use malloc it returns a pointer to void – after all it has no idea what you are allocating the memory for.

In most cases to use the memory you need to cast the pointer to void to something else.

For example:

char *myPointer=(char*)malloc(100);

allocates 100 bytes of memory and sets myPointer to its address. The pointer to void is cast to a pointer to char so after this you can use myPointer as if is was an array of char or as a raw pointer to char complete with pointer arithmetic. Note that C99 doesn’t require the explicit cast and will perform the cast automatically, however it is still good practice to write the cast to explicitly indicate what you intend.

For example both:

myPointer[50]=’A’;

and

*(myPointer+50)=’A’;

work perfectly.

This is how you convert a block of allocated memory into something like an array of any type you care to use.

It is usual to express the size of the block of memory to be allocated in terms of the size of the proposed elements. That is you would write:

char *myPointer=(char*)malloc(100*sizeof(char));

to allocate an “array” of 100 char. The same works for any type of element:

type pointer=malloc(n*sizeof(type))

and is the pointer equivalent of declaring an array of n elements.

But we already know how to create an array.

The real advantage of allocating memory on the heap is that it is dynamic. For example, if you want to create an array that can change its size you need to write something like:

char *myCharArray=(char *)malloc(100);
for(int i=0;i<100;i++){
     myCharArray[i]=i;
}

This sets up an array of 100 elements on the heap and sets each element to its position value in the array using array notation. Now suppose the array is too small and you want to add another 100 elements alll you need to do is realloc:

myCharArray=(char *)realloc(myCharArray,200);
for(int i=100;i<200;i++){
     myCharArray[i]=i;
}

Notice that we have set the new elements to their position in the array – but as this is only an eight bit element this “rolls over” at 127 to negative values according to 2’s complement representation.

You can check the contents of the array using:

for(int i=0;i<200;i++){
    printf("%d\n",myCharArray[i]);
}

Notice that the system has copied the existing elements to the new block of memory – this is usually implemented more efficiently than you could write the same process in C.

However it is worth point out that there is more flexibility in using pointers than than just changing the size of the allocation suggests. You can create as many pointers to the block of memory as you need and cast them accordingly.

For example, suppose you want to use the block of memory to store either chars or ints then all you have to do is create two appropriate pointers:

char *myCharPointer = (char*) malloc(100);
int *myIntPointer = (int*) myCharPointer;

Now you have two pointers that point to the same block of memory but one treats it as an array of char and the other an array of int.

What practical use is this?



Last Updated ( Monday, 10 September 2018 )