7

I think that it is because the former is an array of pointers to char and the latter is a pointer to an array of chars, and we need to properly specify the size of the object being pointed to for our function definition. In the former;

function(char * p_array[])

the size of the object being pointed to is already included (its a pointer to char), but the latter

function(char (*p_array)[])

needs the size of the array p_array points to as part of p_array's definition? I'm at the stage where I've been thinking about this for too long and have just confused myself, someone please let me know if my reasoning is correct.

Matt
  • 165
  • 1
  • 7

4 Answers4

16

Both are valid in C but not C++. You would ordinarily be correct:

char *x[]; // array of pointers to char
char (*y)[]; // pointer to array of char

However, the arrays decay to pointers if they appear as function parameters. So they become:

char **x; // Changes to pointer to array of pointer to char
char (*y)[]; // No decay, since it's NOT an array, it's a pointer to an array

In an array type in C, one of the sizes is permitted to be unspecified. This must be the leftmost one (whoops, I said rightmost at first). So,

int valid_array[][5]; // Ok
int invalid_array[5][]; // Wrong

(You can chain them... but we seldom have reason to do so...)

int (*convoluted_array[][5])[][10];

There is a catch, and the catch is that an array type with [] in it is an incomplete type. You can pass around a pointer to an incomplete type but certain operations will not work, as they need a complete type. For example, this will not work:

void func(int (*x)[])
{
    x[2][5] = 900; // Error
}

This is an error because in order to find the address of x[2], the compiler needs to know how big x[0] and x[1] are. But x[0] and x[1] have type int [] -- an incomplete type with no information about how big it is. This becomes clearer if you imagine what the "un-decayed" version of the type would be, which is int x[][] -- obviously invalid C. If you want to pass a two-dimensional array around in C, you have a few options:

  • Pass a one-dimensional array with a size parameter.

    void func(int n, int x[])
    {
        x[2*n + 5] = 900;
    }
    
  • Use an array of pointers to rows. This is somewhat clunky if you have genuine 2D data.

    void func(int *x[])
    {
        x[2][5] = 900;
    }
    
  • Use a fixed size.

    void func(int x[][5])
    {
        x[2][5] = 900;
    }
    
  • Use a variable length array (C99 only, so it probably doesn't work with Microsoft compilers).

    // There's some funny syntax if you want 'x' before 'width'
    void func(int n, int x[][n])
    {
        x[2][5] = 900;
    }
    

This is a frequent problem area even for C veterans. Many languages lack intrinsic "out-of-the-box" support for real, variable size, multidimensional arrays (C++, Java, Python) although a few languages do have it (Common Lisp, Haskell, Fortran). You'll see a lot of code that uses arrays of arrays or that calculates array offsets manually.

mk12
  • 25,873
  • 32
  • 98
  • 137
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • When I try to pass a multidimensional array as the actual argument to a function with the formal parameter (char (*y)[]) I get error: invalid use of array with unspecified bounds. Does this mean that while the definition is valid, it is not actually useful for anything? – Matt Aug 04 '11 at 05:15
  • Are you *absolutely sure* you are compiling with a C compiler, and *not* a C++ compiler? These kind of array declarations are dirt common in some C programming styles. – Dietrich Epp Aug 04 '11 at 05:16
  • Tried it both using codeblocks C compiler and codepad online. Online codepad:http://codepad.org/HJneQAbW – Matt Aug 04 '11 at 05:22
  • `char (*p_array)[]` is a pointer to an incomplete type, so you can't dereference it. – caf Aug 04 '11 at 06:05
  • @Matt: The error is not in the function declaration but in the body of the function (line 18). See edit. – Dietrich Epp Aug 04 '11 at 06:14
  • @caf: Technically, incomplete types can be dereferenced but you cannot add an integer to a pointer to an incomplete type. So `(*p_array)[j]` is valid but `p_array[i][j]` is not. – Dietrich Epp Aug 04 '11 at 06:26
  • Ahh, that edited explanation makes a lot of sense. Thanks for your time and help! – Matt Aug 04 '11 at 06:29
3

NOTE:
The below answer was added when the Q was tagged C++, and it answers from a C++ perspective. With tagged changed to only C, both the mentioned samples are valid in C.

Yes, Your reasoning is correct.
If you try compiling the error given by compiler is:

parameter ‘p_array’ includes pointer to array of unknown bound ‘char []’

In C++ array sizes need to be fixed at compile time. C++ standard forbids Variable Lenght Array's(VLA) as well. Some compilers support that as an extension but that is non standard conforming.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
3

Those two declarations are very different. In a function parameter declaration, a declarator of [] directly applied to the parameter name is completely equivalent to a *, so your first declaration is exactly the same in all respects as this:

function(char **p_array);

However, this does not apply recursively to parameter types. Your second parameter has type char (*)[], which is a pointer to an array of unknown size - it is a pointer to an incomplete type. You can happily declare variables with this type - the following is a valid variable declaration:

char (*p_array)[];

Just like a pointer to any other incomplete type, you cannot perform any pointer arithmetic on this variable (or your function parameter) - that's where you error arises. Note that the [] operator is specified as a[i] being identical to *(a+i), so that operator cannot be applied to your pointer. You can, of course, happily use it as a pointer, so this is valid:

void function(char (*p_array)[])
{
    printf("p_array = %p\n", (void *)p_array);
}

This type is also compatible with a pointer to any other fixed-size array of char, so you can also do this:

void function(char (*p_array)[])
{
    char (*p_a_10)[10] = p_array;

    puts(*p_a_10);
}

...and even this:

void function(char (*p_array)[])
{
    puts(*p_array);
}

(though there is precious little point in doing so: you might as well just declare the parameter with type char *).

Note that although *p_array is allowed, p_array[0] is not.

caf
  • 233,326
  • 40
  • 323
  • 462
  • 1
    So basically - the function definition (char (*p_array)[]) is allowed, and function can use the p_array pointer and dereference it to obtain values, but can't use the p_array pointer itself for arithmetic. So we can't do arithmetic (like *(p_array+1)) on the p_array pointer, because it doesn't know how much +1 should add to p_array, because it doesn't know what type it points to. BUT arithmetic like *(*(p_array)+1) should be valid right, because it knows that *p_array is a pointer to type char and therefore how much +1 should add? – Matt Aug 04 '11 at 06:52
  • 1
    @Matt: Yes. That second example is equivalent to `(*p_array)[1]`, which is OK. – caf Aug 04 '11 at 06:57
2

Because,

(1) function(char * p_array[])

is equivalent to char **p_array; i.e. a double pointer which is valid.

(2) function(char (*p_array)[])

You are right, that p_array is pointer to char array. But that needs to be of fixed size in the case when it appears as function argument. You need to provide the size and that will also become valid.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Is (2) **pointer to char array** or **a function pointer to a char array** ? – Mahesh Aug 04 '11 at 04:42
  • @Mahesh, no the 2nd one is pointer to character array (but need to be of fixed size). Also character array is a data type; we cannot have a function pointer pointing to it. You can assign as, `char (*p)[3] = &a;` where `char a[3];`. – iammilind Aug 04 '11 at 04:44
  • @iammilind: The size does not need to be declared, and these compile without error on my system. – Dietrich Epp Aug 04 '11 at 04:48
  • @Dietrich, in the case of function argument, we need to have size declared. http://www.ideone.com/DRIeO – iammilind Aug 04 '11 at 04:49
  • 1
    @iammilind: The question title says "C", but you are compiling as C++. – Dietrich Epp Aug 04 '11 at 04:51
  • 1
    @Dietrich Epp: The Q is tagged both C & C++, the title talks of only C. The behaviors will be different in C & C++. C allows VLA while C++ does not. – Alok Save Aug 04 '11 at 04:55
  • @Dietrich - "Compiles without error on my system" doesn't mean "Is legal in standard C." – Chris Lutz Aug 04 '11 at 05:03
  • 2
    @Chris: I apologize. I am often wrong about things and use the compiler `-pendantic -ansi -Wall -Wextra` to double check, as finding the appropriate section takes more time. (The appropriate section is n1570 section 6.7.6.2 and 6.7.6.3, although you basically have to read both sections to figure out that this is okay. They are not VLAs, but are instead arrays of incomplete type. VLAs are new in C99, but arrays of incomplete type are as old as C itself.) – Dietrich Epp Aug 04 '11 at 05:15
  • @Dietrich - That makes sense. I now think you are right, though I consider it unfortunate that this code is valid. – Chris Lutz Aug 04 '11 at 05:36