Void pointers pretending to be void double pointers

2.3k views Asked by At

I've been doing some thinking. I haven't found anything directly answering this question, but I think I know the answer; I just want some input from some more experienced persons.

Knowns:

A void pointer points to just a memory address. It includes no type information.

An int pointer points to a memory address containing an int. It will read whatever is in the memory address pointed to as an integer, regardless of what was stuffed into the address originally.

Question:

If a void double pointer void ** foo were to point to a dynamically allocated array of void pointers

void ** foo = malloc(sizeof(void *) * NUM_ELEMENTS);

is it true, as I am supposing, that because of the unique nature of void pointers actually lacking any sort of type information that instead of void ** foo an equivalent statement would be

void * bar = malloc(sizeof(void *) * NUM_ELEMENTS);

and that when I use indirection to access by assigning a specific type, such as with

(It was pointed out that I can't dereference void pointers. For clarity to the purpose of the question the next line is changed to be appropriate to that information)

int ** fubar = bar;

that I would get an appropriate pointer from the single void pointer which is just acting like a double pointer?

Or is this all just in my head?

2

There are 2 answers

11
Eric Postpischil On BEST ANSWER

It is permissible to assign the result of malloc to a void * object and then later assign it to an int ** object. This is because the return value of malloc has type void * anyway, and it is guaranteed to be suitable for assignment a pointer to any type of object with a fundamental alignment requirement.

However, this code:

#define NUM_ELEMENTS 1000
void *bar = malloc(sizeof(void *) * NUM_ELEMENTS);
int **fubar = bar;
*fubar = 0;

is not guaranteed by the C standard to work; it may have undefined behavior. The reason for this is not obvious. The C standard does not require different types of pointers to have the same size. A C implementation may set the size of an int * to one million bytes and the size of a void * to four bytes. In this case, the space allocated for 1000 void * would not be enough to hold one int *, so the assignment to *fubar has undefined behavior. Generally, one would implement C in such a way only to prove a point. However, similar errors are possible on a smaller scale: There are C implementations in which pointers of different types have different sizes.

A pointer to an object type may be converted to a pointer to another object type provided the pointer has alignment suitable for the destination type. If it does, then converting it back yields a pointer with the original value. Thus, you may convert pointers to void * to pointers to void and back, and you may convert pointers to void * to pointers to int * and back, provided the alignments are suitable (which they will be if the pointers were returned by malloc and you are not using custom objects with extended alignments).

In general, you cannot write using a pointer to an object type and then read the same bytes using a pointer to a different object type. This violates aliasing rules. An exception is that if one of the pointers is to a character type. Also, many C implementations do support such aliasing, but it may require setting command-line options to enable such support.

This prohibition on aliasing includes reinterpreting pointers. Consider this code:

int a;
int *b = &a;
void **c = (void **) &b;
void *d = *c;
int *e = (int *) d;

In the fourth line, c points to the bytes that b occupies but *c tries to interpret those bytes as a void *. This is not guaranteed to work, so the value that d gets is not necessarily a pointer to a, even when it is converted to int * as in the last line.

1
Gene On

Under the C Standard, the behavior of the code you gave is undefined because you allocated an array of void pointers and then tried to use it as an array of int pointers. There is nothing in the Standard that requires these two kinds of pointer to have the same size or alignment. Now if you had said

void * bar = malloc(sizeof(int*) * NUM_ELEMENTS);
int ** fubar = bar;

Then all would be fine.

Now on the vast majority of machines, an int* and a void* will actually have the same size and alignment. So your code ought to work fine in practice.

Additionally, these two are not equivalent:

void ** foo = malloc(sizeof(void *) * NUM_ELEMENTS);
void * bar = malloc(sizeof(void *) * NUM_ELEMENTS);

This is because foo can be dereferenced at any element to get a void pointer, while bar cannot. For example, this program is correct and prints 00000000 on my 32-bit machine:

#include <stdio.h>
#include <stdlib.h>

int main(void) 
{
  void **a = calloc(10, sizeof(void*));
  printf("%p\n", a[0]);
  return 0;
}

One other point is that you seem to be thinking that type information is explicit in the pointer at the machine level. This is not true (at least for the vast majority of implementations). The type of C pointers is normally represented only while the program is being compiled. By the time compilation is done, explicit type information is normally lost except in debugging symbol tables, which are not runnable code. (There are some minor exceptions to this. And for C++ the situation is very different.)