5

I'm getting the following warning: incompatible pointer types 'void**' and 'int* [2]'.

When I try to compile the following code:

#include <stdlib.h>

void func1(void *arr[]) { }

int main() {
    int *arr[2];
    for (int i = 0; i < 5; i++) {
        arr[i] = (int*)malloc(sizeof(int));
        *(arr[i]) = 5;
    }
    func1(arr);
}

Now, it works when I cast arr with (void**), and I couldn't find a reason for that. Furthermore I found that I also need to cast in the following code:

#include <stdlib.h>

void func1(void **arr) { }

int main() {
    int **arr;
    int i[] = { 1, 2 };
    int j[] = { 3, 4 };
    *arr = i;
    *(arr+1) = j;
    func1(arr); //Doesn't compile unless I use (void*) or (void**) casting
}

I know that if a function's parameter is a pointer to void we can pass to it whatever pointer we want without casting because all pointers are of the same size then why can't I pass a pointer to a pointer the same way?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Uri Greenberg
  • 152
  • 1
  • 11

2 Answers2

4

There are several problems with your second code snippet:

void func1(void** arr) { }
int main() {
    int** arr;
    int i[] = {1,2};
    int j[] = {3,4};
    *arr = i;
    *(arr+1) = j;
    func1(arr); //Doesn't compile unless I use (void*) or (void**) casting
}

You never initialize arr to point to a pointer or an array of pointers to int. It is uninitialized, so its value can be anything. When you set *arr to i, you invoke undefined behavior.

Furthermore, int ** and void ** are not interoperable types, you cannot convert one to the other implicitly. The rationale for this is that on some rare systems, int * and void * may have a different representation. Casting a pointer to int * as a pointer to void * would be as incorrect as casting a pointer to float as a pointer to double. On systems where the representation is the same, you can just write the explicit cast.

Here is a corrected version:

#include <stdlib.h>

void func1(void **arr) { }

int main(void) {
    int *int_pointer_array[2];
    int **arr = &int_pointer_array;
    int i[] = { 1, 2 };
    int j[] = { 3, 4 };
    *arr = i;     /* this line modifies int_pointer_array[0] */
    *(arr+1) = j; /* this line modifies int_pointer_array[1] */
    func1((void **)arr);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • The "corrected version" still causes undefined behaviour even if the pointer types had the same size and representation. It also violates the strict aliasing rule (`int *` is aliased as `void *`) – M.M Jan 26 '16 at 22:46
  • @M.M: it does, but in separate functions, unlikely to cause a problem if the representation of `int *` and `void *` are the same. – chqrlie Jan 26 '16 at 23:07
2

All object pointer types, including int *, are guaranteed to be interconvertible with void *, but they are not interchangeable. The representation of type int * does not have to be the same as the representation of type void * (though in practice, it almost always is), so there is no automatic conversion from a pointer to int * (i.e. int **) to a pointer to void * (i.e. void **). It is not safe to assume that a pointed-to thing of one type can be correctly re-interpreted as a thing of the other pointed-to type.

Note, by the way, that this:

if a function's parameter is a pointer to void we can pass to it whatever pointer we want without casting because all pointers are of the same size

is an incorrect characterization. Pointers are not required to all be the same size. It is required only that every object pointer can be converted to type void * and back, and that the result of such a round-trip conversion is equal to the original pointer.

Do take @Lundin's comments to heart: arrays are not pointers. Nevertheless, values of array type do decay to pointers under most circumstances, including when they appear as function arguments or as the right-hand operand of an assignment operator.

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