1

I know that the C standard promises that a pointer to any object can roundtrip to a void * and back. In other words,

T obj;
assert(&obj == (T *) (void *) &obj);

Now, I'm wondering about pointer arithmetic. How does it follow / where does it say that

T *arr[2] = {0};
assert(arr[1] == (T *) (((void **)arr)[1]))

works? Is this even guaranteed?

I have a hunch that the answer is "it's not guaranteed" or even "this is UB". The reason is that, as far as I "know", the representation of void * need not equal the representation of other pointers. For example, if void pointers are bigger in size than other pointers, pointer arithmetic on void ** probably couldn't work. So unless the above is legal, I wonder what should I do in practice if I want to operate on pointer arrays with generic code.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Jo So
  • 25,005
  • 6
  • 42
  • 59
  • 1
    Nothing in the C standard guarantees this will work. As you note, nothing guarantees a `void *` and a `T *` have the same representation or size. But, even if they do, accessing a element of the array, which is a `T *`, using an lvalue of type `void *`, violates the aliasing rules in C 2018 6.5 7. The behavior is not defined by the standard, so the `assert` is not guaranteed to fail even if, in some sense, the left and right operands have the same value. – Eric Postpischil Nov 01 '20 at 15:20
  • This topic is [discussed here](https://stackoverflow.com/a/9040946/645128). Does it answer your question? – ryyker Nov 01 '20 at 15:21
  • 1
    If you **know** your program will execute in a C implementation in which the pointer types being used have the same sizes and representations, then a defined way to use the pointers is to use `unsigned char *` pointers to get bytes out of the array, copy the bytes into a `void *`, and then use a cast to convert the `void *` to the desired type. – Eric Postpischil Nov 01 '20 at 15:23

1 Answers1

1

I have a hunch that the answer is "it's not guaranteed" or even "this is UB". The reason is that, as far as I "know", the representation of void * need not equal the representation of other pointers. For example, if void pointers are bigger in size than other pointers, pointer arithmetic on void ** probably couldn't work.

You are right, and your reasoning is right, at least in a practical sense. From the perspective of the standard, it's a little unclear which way to argue that evaluating the expression ((void **)arr)[1] produces undefined behavior. I would be inclined to go with this: the expression is equivalent to *(((void **)arr) + 1), and the pointer addition produces undefined behavior on account of (void **)arr not actually pointing to an array of void *. But there is also the fact that the type of *(((void **)arr) + 1) is void *, and, per the strict aliasing rule, evaluating that produces undefined behavior if it does not, in fact, refer to a void *.

I wonder what should I do in practice if I want to operate on pointer arrays with generic code.

C does not have a mechanism for expressing a generic array-of-pointers type. In particular, arrays of the form void *arr[DIMENSION] and pointers of type void ** are not generic. You can, however, use arrays and pointers of those specific types, and load them up with pointers converted from arbitrary other (object) pointer types.

Depending on what specific operations you want to perform, there may be other alternatives.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157