1

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?

ciphermagi
  • 747
  • 3
  • 14
  • 1
    You can't dereference a void pointer. I don't think you can do pointer arithmetic with it in the absence of compiler extensions either. You need to cast it first. – user2357112 Dec 02 '13 at 21:26
  • What about using int ** fubar = bar; ? – ciphermagi Dec 02 '13 at 21:27
  • Might work, I think, until you try to dereference it. If you cast a pointer to the wrong type and try to dereference it, C will not perform conversions to try to make the operation make sense. You will get undefined behavior. – user2357112 Dec 02 '13 at 21:29

2 Answers2

3

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Okay, then to further expound, what would be the consequence of void * foo = malloc(sizeof(int *) * NUM); and then casting that later into int ** bar = foo; and following up with for(...) *(bar + i) = malloc(sizeof(int) * LEN);? – ciphermagi Dec 02 '13 at 22:00
  • @JonahNelson: That is perfectly fine, as far as the C standard is concerned. `malloc` returns a `void *`, so the assignment to `foo` just remembers that value without changing it. Then initializing `int **bar` with `foo` just sets `bar` to that value the same way a normal `int **bar = malloc(…)` would. – Eric Postpischil Dec 02 '13 at 22:05
  • So then effectively there is no purpose in having a void ** foo or void *** bar or anything similar, because void * fubar can do the same job *as long as it is properly cast back to the correct pointer*? – ciphermagi Dec 02 '13 at 22:07
  • @JonahNelson: A major purpose in having different types is to show how you intend to use data so that the compiler can check your use and catch errors when you compile instead of years later when your application is deployed and an error that was not caught previously starts a fire. You **ought** to assign the result of `malloc` to a pointer for the type you intend to use the allocated space for. – Eric Postpischil Dec 02 '13 at 22:08
  • lol...I get that. As far as assigning the result of malloc, it's more about portability of libraries that want to be able to handle different data type and just return void pointers themselves. – ciphermagi Dec 02 '13 at 22:09
  • @ciphermagi "So then effectively there is no purpose in having a void \*\*" - of course there is! The purpose in having a void** is to point to a void*. – user253751 Nov 26 '15 at 01:02
  • Is the example of 1 million bytes for `int*` and 4 for `void*` even hypothetically correct? Forget the same size for every pointer type (e.g. 8 bytes). Even if there are different sizes, doesn’t `void*` have to be the _largest_, if it’s required to be able to store _any_ pointer? – Rich Jahn Dec 21 '21 at 05:09
  • @RichJahn: The C standard only requires that `void *` be able to hold the address information of any pointer to object type, i.e., that converting to `void *` and back yields a pointer that will compare equal. It does not require that `void *` be able to hold all the bits in an `int *`. For example, one could use extra bits in `int *` to hold a checksum for authentication (e.g., there is some ARM feature for authenticated pointers) or to hold debugging information, or it could simply be unused padding. A round-trip through `void *` would not preserve this data but would satisfy the C standard. – Eric Postpischil Dec 21 '21 at 11:53
  • @EricPostpischil I'm still confused. So with: ```int *p = get_int_from_db(); void *v = p; int *alias = v; if (check_ptr(p)) printf("Is valid\n"); if (!check_ptr(alias)) printf("Not valid\n"); if (p == alias) printf("Pointers are equal\n");``` You can have additional bits stored in the pointer for extra information, which is lost, but the pointers can still compare equal? – Rich Jahn Dec 21 '21 at 18:18
  • @EricPostpischil Or maybe you mean `p != alias` is true (in my example), but with `*p = 25;`,then `*alias == 25` is true? (i.e. not equal, but necessary address information is there). You did say they "will compare equal" though. Or maybe you mean the extra data is completely invisible in C code, but still there in machine code? So maybe my example is possible if the `check_ptr()` had some assembly in its implementation? – Rich Jahn Dec 21 '21 at 18:55
  • @RichJahn: Yes, you can have additional bits in a pointer which are not preserved when it is converted to `void *`, and the result of converting it back to `int *` can (and should, per the C standard) compare equal to the original pointer; `p == alias` will evaluate as true. – Eric Postpischil Dec 21 '21 at 20:21
0

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.)

Gene
  • 46,253
  • 4
  • 58
  • 96