bwoebi
bwoebi

Reputation: 23777

Unable to pass an address of array of type char *[2] to function taking char ***

My XCode (default compiler should be clang?) shows me on this code a warning:

Incompatible pointer types passing 'char *(*)[2]' to parameter of type 'char ***' (when calling func)

void func (char ***arrayref, register size_t size) {
    /// ...
}

int main()
{
    char *array[2] = {"string", "value"};
    func(&array, 2);
}

while this code is no problem (=no warning):

void func (char **arrayref, register size_t size) {
    /// ...
}

int main()
{
    char *array[2] = {"string", "value"};
    func(array, 2);
}

While this removes the warning

func((char***)&array, 2);

I still don't know why the first emits a warning, while the latter doesn't.

Also, when the first is a problem, I'd also expect that the first emits a warning like:

Incompatible pointer types passing 'char *[2]' to parameter of type 'char **'

Upvotes: 12

Views: 10565

Answers (4)

LihO
LihO

Reputation: 42083

You have an array of type char *[2] i.e. array of 2 pointers to char. It is an array of fixed size with automatic storage duration. The only thing that your function can do with this kind of array is to either use its elements or to change them (it can not resize it or deallocate it... therefore it makes no sense to try to make it possible to change the array itself ~> in other words: you don't really need a pointer to this kind of array).

Here's a simple example:

void func (char *arrayref[2]) {
    printf("%s", arrayref[1]);
    arrayref[1] = "new value";
}

int main() {    {
    char *array[2] = {"string", "value"};
    func(array);
    printf(" -> %s", array[1]);
    return 0;
}

or alternatively changing func to take an array of unspecified size making it usable with char *[X] for any X, not just 2 (in that case it makes sense already to pass the array's size):

void func (char *arrayref[], size_t size) {
    if (size > 1) {
        printf("%s", arrayref[1]);
        arrayref[1] = "new value";
    }
}

with one way or other, this program would output value -> new value.

If you need your function to be able to resize this array or affect the array itself in some other way, you should consider using dynamically-allocated array and passing in form of char**.

Upvotes: 2

John Bode
John Bode

Reputation: 123468

Time for a brief refresher on array semantics.

Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element in the array.

The expression array in your code has type "2-element array of char *", or char *[2]. When you pass it as an argument to func, as in

func( array, 2 );

the expression is converted to an expression of type "pointer to char *", or char **, which is the type your function is expecting, and the value of the expression is the address of the first element: array == &array[0]. This is why you don't get the warning for the second code snippet.

In the first snippet, the array is an operand of the unary & operator, so the conversion to a pointer type doesn't happen; instead, the type of the expression is "pointer to 2-element array of char *", or char *(*)[2], which is not compatible with char **. The address value is the same (the address of the first element of the array is the same as the address of the array itself), but the types don't match, hence the warning.

So why does this matter? A pointer is just an address, and all address are the same, right? Well, no. A pointer is an abstraction of an address, with associated type semantics. Pointers to different types don't have to have the same size or representation, and pointer arithmetic depends on the type of the pointed-to type.

For example, if I declare a pointer as char **p;, then the expression p++ will advance the pointer to point to the next object of type char *, or sizeof (char *) bytes from the current address. If p is declared as char *(*p)[2], however, the expression p++ will advance p to point to the next two-element array of char *, which is 2 * sizeof (char *) bytes from the current address.

Upvotes: 15

dcaswell
dcaswell

Reputation: 3167

Here's an example of how to do what you want and change what array points to:

char *array2[] = {"string", "NewValue"};

void func0 (char **arrayref, register size_t size) {
    puts(arrayref[1]);
}

void func1 (char ***arrayref, register size_t size) {
    puts(arrayref[0][1]);
    *arrayref= (char **) array2;

}

int main()
{
    char *array[] = {"string", "value"};
    char **foo = array;
    func0(foo, 2);
    func1(&foo,2);
    func0(foo, 2);
}

Upvotes: 3

glglgl
glglgl

Reputation: 91049

char *array[2] = {"string", "value"};

is an array with 2 elements of char *.

Using array as an address results to a pointer to the first element, i. e. of type char **.

Using &array results to a pointer to the same place, but of type char *(*)[2] (not sure if the spelling is right).

This is not the same as a char *** - the representation in memory is completely different.

To be more verbose,

+++++++++++++++++++++++
+ array[0] + array[1] +
+++++++++++++++++++++++

this is the array.

char ** p1 = array; // is a pointer to the first element, which in turn is a pointer.
char *(*p2)[2] = &array; // is a pointer to the whole array. Same address, but different type, i. e. sizeof(*p1) != sizeof(*p2) and other differences.

char ***p3 = &p1; // Now, p1 is a different pointer variable which has an address itself which has type `char ***`.

Upvotes: 6

Related Questions