C pointer notation compared to array notation: When passing to function - c

My question is base on the following code:
int myfunct(int ary[], int arysize)
int myfunct2(int *ary, int arysize)
int main(void){
int numary[10];
myfunct(numary, 10)
myfunct2(numary, 10)
return;
}
int myfunct(int ary[], int arysize) {
//Whatever work is done
}
int myfunct2(int *ary, int arysize) {
// Whatever work is done
}
Is there a reason to use one of these over the other? To elaborate, when concerned with numeric arrays, is there any reason one would want to use pointer notation over array notation. If one uses pointer notation then within the function pointer arithmetic would be used etc.. AND if one uses the [] array notation, one could work with the array as usual. I'm new to programming and I currently do not see any benefit to using the pointer notation.
My precise question, is there any reason to pass a numeric array to a function using pointer notation and therefore using pointer manipulations within the function.

When you declare a function parameter as an array, the compiler automatically ignores the array size (if any) and converts it to a pointer. That is, this declaration:
int foo(char p[123]);
is 100% equivalent to:
int foo(char *p);
In fact, this isn't about notation but about the actual type:
typedef char array_t[42];
int foo(array_t p); // still the same function
This has nothing to do with how you access p within the function. Furthermore, the [] operator is not "array notation". [] is a pointer operator:
a[b]
is 100% equivalent to:
*(a + b)

There is no real functional difference between the two notations. In C, when you pass an array variable to a function, it decays to a pointer regardless of the notation. However, in my opinion, the pointer notation is preferable. The problem with [] notation in function definitions is that, in my opinion, it is somewhat misleading:
void foo(int array[])
{
}
A ubiquitous mistake among novice C programmers is to assume that sizeof(array) will give you the number of elements in the array multiplied by sizeof(int), like it would if array were an array variable declared on the stack. But the reality is that array has been decayed to a pointer, despite the misleading [] notation, and so sizeof(array) is going to be sizeof(int*). array is really just a pointer to the first element, or possibly a pointer to a single integer allocated anywhere.
For example, we could call foo like this:
int x = 10;
foo(&x);
In which case the [] notation in the definition of foo is kind of misleading.

Those declarations are absolutely identical. To quote the standard:
A declaration of a parameter as "array of type" shall be adjusted to
"qualified pointer to type"
C99 standard section 6.7.5.3 paragraph 7

In modern C that has variable length arrays since C99, the array notation is preferable if is an array, I think. For one dimensional arrays, you can do things like
int myfunct(size_t size, int array[size]) {
... array[i] ..
}
and for two dimensional
int myfunct(size_t size, int array[size][size]) {
... array[i][j] ..
}
So array notation fits much better in the general picture. A sophisticated compiler could then even do bounds checking, but I don't know of any that does this yet.

In my opinion, the main reason to prefer pointer notation over empty array notation in function prototypes is that the latter is not consistent with structure definitions:
struct person {
char *firstname;
char *lastname;
};
void setperson(struct person *p, char firstname[], char lastname[])
{
p->firstname = firstname;
p->lastname = lastname;
}
In structures you will have to use the pointer notation anyway because empty array notation is only valid, at least since C99, for the last member when you want to make it a flexible array member.

You only need to use the array notation for multidimensional arrays. (You do not have to provide the size of the first dimension).

Related

Differences when using ** in C

I started learning C recently, and I'm having a problem understanding pointer syntax, for example when I write the following line:
int ** arr = NULL;
How can I know if:
arr is a pointer to a pointer of an integer
arr is a pointer to an array of pointers to integers
arr is a pointer to an array of pointers to arrays of integers
Isn't it all the same with int ** ?
Another question for the same problem:
If I have a function that receives char ** s as a parameter, I want to refer to it as a pointer to an array of strings, meaning a pointer to an array of pointers to an array of chars, but is it also a pointer to a pointer to a char?
Isn't it all the same with int **?
You've just discovered what may be considered a flaw in the type system. Every option you specified can be true. It's essentially derived from a flat view of a programs memory, where a single address can be used to reference various logical memory layouts.
The way C programmers have been dealing with this since C's inception, is by putting a convention in place. Such as demanding size parameter(s) for functions that accept such pointers, and documenting their assumptions about the memory layout. Or demanding that arrays be terminated with a special value, thus allowing "jagged" buffers of pointers to buffers.
I feel a certain amount of clarification is in order. As you'd see when consulting the other very good answers here, arrays are most definitely not pointers. They do however decay into ones in enough contexts to warrant a decades long error in teaching about them (but I digress).
What I originally wrote refers to code as follows:
void func(int **p_buff)
{
}
//...
int a = 0, *pa = &a;
func(&pa);
//...
int a[3][10];
int *a_pts[3] = { a[0], a[1], a[2] };
func(a_pts);
//...
int **a = malloc(10 * sizeof *a);
for(int i = 0; i < 10; ++i)
a[i] = malloc(i * sizeof *a[i]);
func(a);
Assume func and each code snippet is compiled in a separate translation unit. Each example (barring any typos by me) is valid C. The arrays will decay into a "pointer-to-a-pointer" when passed as arguments. How is the definition of func to know what exactly it was passed from the type of its parameter alone!? The answer is that it cannot. The static type of p_buff is int**, but it still allows func to indirectly access (parts of) objects with vastly different effective types.
The declaration int **arr says: "declare arr as a pointer to a pointer to an integer". It (if valid) points to a single pointer that points (if valid) to a single integer object. As it is possible to use pointer arithmetic with either level of indirection (i.e. *arr is the same as arr[0] and **arr is the same as arr[0][0]) , the object can be used for accessing any of the 3 from your question (that is, for second, access an array of pointers to integers, and for third, access an array of pointers to first elements of integer arrays), provided that the pointers point to the first elements of the arrays...
Yet, arr is still declared as a pointer to a single pointer to a single integer object. It is also possible to declare a pointer to an array of defined dimensions. Here a is declared as a pointer to 10-element array of pointers to arrays of 10 integers:
cdecl> declare a as pointer to array 10 of pointer to array 10 of int;
int (*(*a)[10])[10]
In practice array pointers are most used for passing in multidimensional arrays of constant dimensions into functions, and for passing in variable-length arrays. The syntax to declare a variable as a pointer to an array is seldom seen, as whenever they're passed into a function, it is somewhat easier to use parameters of type "array of undefined size" instead, so instead of declaring
void func(int (*a)[10]);
one could use
void func(int a[][10])
to pass in a a multidimensional array of arrays of 10 integers. Alternatively, a typedef can be used to lessen the headache.
How can I know if :
arr is a pointer to a pointer of an integer
It is always a pointer to pointer to integer.
arr is a pointer to an array of pointers to integers
arr is a pointer to an array of pointers to arrays of integers
It can never be that. A pointer to an array of pointers to integers would be declared like this:
int* (*arr)[n]
It sounds as if you have been tricked to use int** by poor teachers/books/tutorials. It is almost always incorrect practice, as explained here and here and (
with detailed explanation about array pointers) here.
EDIT
Finally got around to writing a detailed post explaining what arrays are, what look-up tables are, why the latter are bad and what you should use instead: Correctly allocating multi-dimensional arrays.
Having solely the declaration of the variable, you cannot distinguish the three cases. One can still discuss if one should not use something like int *x[10] to express an array of 10 pointers to ints or something else; but int **x can - due to pointer arithmetics, be used in the three different ways, each way assuming a different memory layout with the (good) chance to make the wrong assumption.
Consider the following example, where an int ** is used in three different ways, i.e. p2p2i_v1 as a pointer to a pointer to a (single) int, p2p2i_v2 as a pointer to an array of pointers to int, and p2p2i_v3 as a pointer to a pointer to an array of ints. Note that you cannot distinguish these three meanings solely by the type, which is int** for all three. But with different initialisations, accessing each of them in the wrong way yields something unpredictable, except accessing the very first elements:
int i1=1,i2=2,i3=3,i4=4;
int *p2i = &i1;
int **p2p2i_v1 = &p2i; // pointer to a pointer to a single int
int *arrayOfp2i[4] = { &i1, &i2, &i3, &i4 };
int **p2p2i_v2 = arrayOfp2i; // pointer to an array of pointers to int
int arrayOfI[4] = { 5,6,7,8 };
int *p2arrayOfi = arrayOfI;
int **p2p2i_v3 = &p2arrayOfi; // pointer to a pointer to an array of ints
// assuming a pointer to a pointer to a single int:
int derefi1_v1 = *p2p2i_v1[0]; // correct; yields 1
int derefi1_v2 = *p2p2i_v2[0]; // correct; yields 1
int derefi1_v3 = *p2p2i_v3[0]; // correct; yields 5
// assuming a pointer to an array of pointers to int's
int derefi1_v1_at1 = *p2p2i_v1[1]; // incorrect, yields ? or seg fault
int derefi1_v2_at1 = *p2p2i_v2[1]; // correct; yields 2
int derefi1_v3_at1 = *p2p2i_v3[1]; // incorrect, yields ? or seg fault
// assuming a pointer to an array of pointers to an array of int's
int derefarray_at1_v1 = (*p2p2i_v1)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v2 = (*p2p2i_v2)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v3 = (*p2p2i_v3)[1]; // correct; yields 6;
How can I know if :
arr is a pointer to a pointer of an integer
arr is a pointer to an array of pointers to integers
arr is a pointer to an array of pointers to arrays of integers
You cannot. It can be any of those. What it ends up being depends on how you allocate / use it.
So if you write code using these, document what you're doing with them, pass size parameters to the functions using them, and generally be sure about what you allocated before using it.
Pointers do not keep the information whether they point to a single object or an object that is an element of an array. Moreover for the pointer arithmetic single objects are considered like arrays consisting from one element.
Consider these declarations
int a;
int a1[1];
int a2[10];
int *p;
p = &a;
//...
p = a1;
//...
p = a2;
In this example the pointer p deals with addresses. It does not know whether the address it stores points to a single object like a or to the first element of the array a1 that has only one element or to the first element of the array a2 that has ten elements.
The type of
int ** arr;
only have one valid interpretation. It is:
arr is a pointer to a pointer to an integer
If you have no more information than the declaration above, that is all you can know about it, i.e. if arr is probably initialized, it points to another pointer, which - if probably initialized - points to an integer.
Assuming proper initialization, the only guaranteed valid way to use it is:
**arr = 42;
int a = **arr;
However, C allows you to use it in multiple ways.
• arr can be used as a pointer to a pointer to an integer (i.e. the basic case)
int a = **arr;
• arr can be used as a pointer to a pointer to an an array of integer
int a = (*arr)[4];
• arr can be used as a pointer to an array of pointers to integers
int a = *(arr[4]);
• arr can be used as a pointer to an array of pointers to arrays of integers
int a = arr[4][4];
In the last three cases it may look as if you have an array. However, the type is not an array. The type is always just a pointer to a pointer to an integer - the dereferencing is pointer arithmetic. It is nothing like a 2D array.
To know which is valid for the program at hand, you need to look at the code initializing arr.
Update
For the updated part of the question:
If you have:
void foo(char** x) { .... };
the only thing that you know for sure is that **x will give a char and *x will give you a char pointer (in both cases proper initialization of x is assumed).
If you want to use x in another way, e.g. x[2] to get the third char pointer, it requires that the caller has initialized x so that it points to a memory area that has at least 3 consecutive char pointers. This can be described as a contract for calling foo.
C syntax is logical. As an asterisk before the identifier in the declaration means pointer to the type of the variable, two asterisks mean pointer to a pointer to the type of the variable.
In this case arr is a pointer to a pointer to integer.
There are several usages of double pointers. For instance you could represent a matrix with a pointer to a vector of pointers. Each pointer in this vector points to the row of the matrix itself.
One can also create a two dimensional array using it,like this
int **arr=(int**)malloc(row*(sizeof(int*)));
for(i=0;i<row;i++) {
*(arr+i)=(int*)malloc(sizeof(int)*col); //You can use this also. Meaning of both is same. //
arr[i]=(int*)malloc(sizeof(int)*col); }
There is one trick when using pointers, read it from right hand side to the left hand side:
int** arr = NULL;
What do you get: arr, *, *, int, so array is a pointer to a pointer to an integer.
And int **arr; is the same as int** arr;.
int ** arr = NULL;
It's tell the compiler, arr is a double pointer of an integer and assigned NULL value.
There are already good answers here, but I want to mention my "goto" site for complicated declarations: http://cdecl.org/
Visit the site, paste your declaration and it will translate it to English.
For int ** arr;, it says declare arr as pointer to pointer to int.
The site also shows examples. Test yourself on them, then hover your cursor to see the answer.
(double (^)(int , long long ))foo
cast foo into block(int, long long) returning double
int (*(*foo)(void ))[3]
declare foo as pointer to function (void) returning pointer to array 3 of int
It will also translate English into C declarations, which is prety neat - if you get the description correct.

Why is char*p[10] considered char** p by the compiler? [duplicate]

This question already has answers here:
Should I use char** argv or char* argv[]?
(10 answers)
Closed 8 years ago.
I've been fiddling around to see if there's any way to retain information about an array's length automatically when passed into a function (see my other question here: Why is this array size "workaround" giving me a warning?), but my question is more about a warning that gcc is giving that doesn't make sense to me.
According to this website (EDIT: I misread the website), char *p[10] declares a pointer to a 10-wide array of chars. But when I tried to pass in a pointer to an array into a function, I got this error message from the compiler:
Here is the rest of the program:
I know that when an array is passed into a function, it decays into a pointer (losing information about its length), but it seems that the declaration itself is decaying. What's going on here?
EDIT: When I replace the char *p[10] with char (*p)[10], it doesn't give the warning anymore, and more importantly, it displays the proper array length: 10. I guess my questions are 1) Why do the parentheses change things? and 2) Is this a well-known workaround or am I relying on some behavior of the compiler that isn't guaranteed? (i.e. that array length info can be passed by indirectly passing in a pointer to it?)
In fact char *p[10] is an array, of length 10, of pointers to char. You are looking for char (*p)[10]. That is a pointer to an array, of length 10, of char.
You might find http://cdecl.org/ a useful resource to help you test your understanding of declarations.
Regarding the discussion surrounding dynamic arrays, you are going to have to accept that once you allocate an array dynamically, the system provides no means for you to recover the length of the array. It is your responsibility to remember that information.
The subject of your question has been answered already but I wanted to address the heart of it, which is "can I encode the length of an array in its type?" Which is in fact what a pointer-to-array does. The real question is whether you can actually gain any brevity or safety from this. Consider that in each scope where you have a declaration of your type, the length still needs to be known a-priori. To show you what I mean let's generalize your example slightly by making 10 a compile-time constant N.
#define N 10
size_t arraylength(char (*arrayp)[N]) {
return sizeof(*arrayp);
}
int main(void) {
char array[N];
assert( arraylength(&array) == N ); //always true
}
So far so good. We didn't have to pass the length of array anywhere. But it's easy to see that anywhere the expression sizeof(*arrayp) is used, we also could have written N. And any place we declare a char(*)[ ], the bracketed length must come from somewhere.
So what if N isn't a compile time constant, and array is either a VLA or a pointer-to-array from malloc? We can still write and call arraysize, but it looks like this:
size_t arraylength(size_t N, char (*arrayp)[N]) {
return sizeof(*arrayp);
}
int main(void) {
size_t N = length_from_somewhere();
char array[N];
assert( arraylength(sizeof(array), &array) == N );
}
In defining arraysize N must still be visible before the declaration of arrayp. In either case, we can't avoid having N visible outside of the declaration of arrayp. In fact, we didn't gain anything over writing arraysize(size_t N, char* array) and passing array directly (which is a bit silly given the purpose of this function.) Both times arraylength could have equally been written return N;
Which isn't to say that array pointers are useless as parameters to functions -- in the opposite situation, when you want to enforce a length, they can provide type checking to make sure somefunc(char (*)[10]); receives a pointer to an array that is really (sans shady casting) 10 elements long, which is stronger than what a construct like [static 10] provides.
Also keep in mind that all of the length measurements above depend on the underlying type being char where length == size. For any larger type, taking the length requires the usual arithmetic e.g.
sizeof(*arrayp)/sizeof((*arrayp)[0])
In C, arrays decay to pointers to their first elements on most uses. In particular, what a function receives is always just a pointer to the first element, the size of the array is not passed with it.
Get a good text on C and read up on arrays.
I've been fiddling around to see if there's any way to retain information about an array's length automatically when passed into a function
The problem is so annoying that lots of programmers would love to have an answer. Unfortunately, this is not possible.
It seems that the declaration itself is decaying
Pointer to an array is not the same as a pointer to a pointer; that is why you are getting an error.
There is no decaying going on in your code, because you are not passing an array in your code sample: instead, you are trying to pass a pointer to an array &p. The pointer to an array of characters is not compatible to the expected type of the function, which is char**. Array size from the declaration is ignored.
You need to keep in mind two things:
1. Arrays are not pointers.
2. Array names decays to pointers (in most cases) when passed as arguments to functions.
So, when you declare
int a[10]; // a is an array of 10 ints
int *b; // b is a pointer to int
both of a and b are of different types. Former is of type int [10] while latter is of type int *.
In case of function parameter
void foo1 (int a[10]); // Actually you are not passing entire array
void foo2 (int a[]); // And that's why you can omit the first dimension.
void foo3 (int *a); // and the compiler interprets the above two third
ain all of the above function declarations is of same data type int *.
Now in your case
unsigned long arraySize(char *p[10]);
you can declare it as
unsigned long arraySize(char *p[]);
and hence
unsigned long arraySize(char **p);
All are equivalent.
char *p[10] char *p[] and char **p all are exactly equivalent but when they are declared as parameter of a function otherwise char *p[10] (an array of 10 pointers to char) and char **p (a pointer to pointer to char)are entirely of different type.
Suggested reading: C-FAQ: 6. Arrays and Pointers explains this in detailed.
Array name itself is a constant pointer. for example int arr[10]={0};
arr contains the address of arr[0]. hence arr equals&arr[0] .
when u pass the arraysize(&p) , you are actually passing a double pointer .
The correct format to pass a array pointer would be arraysize(&p[0]) or arraysizeof(p)
Note Array name is constant pointer , you cant change its value .
int arr[10];
arr++;
is invalid.
In your case you cant find a size of an array in function by passing the array name . it would return size of pointer(4 or 8 depends on your processor .
The method is to pass the size along with the array
func(array_name , array_size);

Is array copied if given as an argument to a function in C

I'm curious if I give an n-dimensional array for n>1 as an argument to a function, are the contents of whole array copied or just the pointer to the address of the first element.
Assume function signature is something like this:
int someFunction(int n, int arr[n][n]);
[Where I just re-learned this yesterday]
This is a C99 extension that is not widely known. The array is still passed by reference as they always have been, but the compiler is able to interpret it as an array similar to the way it handles Variable-Length Arrays.
This won't give you bound-checking, of course. C doesn't have that.
In your function signature,
int someFunction(int n, int arr[n][n]);
The final n doesn't really buy you anything, it just gets ignored. But int arr[n][] is new. That's what C89 didn't have. Previously, the only option was to calculate indices manually off of the base pointer, arr[n*x+y].
In Ansi-C (C89), you'd have to declare the function as,
int someFunction(int n, int arr[]);
which is equivalent to
int someFunction(int n, int *arr);
and calculate the two dimensions as a single index.
The new C99 magic, reduces the original to this:
int someFunction(int n, int *arr[n]);
I'd describe the process more as array adoption, than passing. It just mitigates some of the losses that were incurred by the original decision to pass arrays as pointers.
It's important to understand that second two examples are sort-of internalized equivalents. Figurative illustrations. In the final one, the array is not converted into an array of pointers, but the bounds of the lowest dimension is dropped. Perhaps it's better illustrated like this:
int someFunction(int n, int arr[n][]);
The only way to copy an array as a function argument is to wrap it in a struct. But then you cannot have variable dimensions.
struct arr {int n; int arr[n][n];}; //Nope, won't compile!
enum { n = 3 };
struct arr { int arr[n][n]; };
struct arr someFunction( struct arr ); //argument and return value are copied.
And this has been legal since 1989.
That's an illegal signature for a function, as arrays in function signatures must have constant dimensions (other than the final dimension).
The reason that arrays must have constant sizes as function parameters is because they aren't actually copied in the function call -- only a pointer is passed.

Pass an array by reference into function argument

I want to manipulate an array of integers in a function, but I am not sure if I should use the address prefix or the pointer prefix:
void shuffle(int *deck[]){
//statements
}
or
void shuffle(int &deck[]){
//statements
}
Further, when I directly want to manipulate the values of the array, I'm not sure if I should use (within the function)
*deck[4] = 34
or something else.
Any clarification is appreciated.
There are no references in C(your Q is tagged C only) so you will have to use the pointer version.
When you say pass an array to the function, what you essentially pass to the function is the pointer to its first element, both of the following two syntaxes mean one and the same thing to the compiler:
void shuffle(int *p_deck);
void shuffle(int deck[]);
Neither.
Since arrays can be passed by reference only, you don't need to do tricks, just pass a pointer and dereference it. (That syntax involving the & in your 2nd function is not valid, anyway). So:
void shuffle(int arr[])
{
arr[0] = 1337;
}
or
void shuffle(int *arr)
{
}
etc. And you can pass it like this:
int deck[52];
shuffle(deck);
You would have to use the pointer version, but because of the Right-Left Rule, I believe your:
int * deck[] would actually be an array of int pointers, instead of an array of ints.
http://cseweb.ucsd.edu/~ricko/CSE131/rt_lt.rule.html
You should just pass a reference to the first element in the array:
int * deck but you may want to pass in the size of the array so you avoid going out of bounds.
Arrays decay into pointers when passed to functions, so there's no point in using an additional level of indirection here.
According to §6.3.2.1 of the C99 standard,
Except when it is the operand of the sizeof operator or the unary & operator, or is a
string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
void shuffle(int deck[]) {
...
}
Note that C does not support references, only pointers. You're probably thinking of C++.
You can pass an array by reference in C++, if you specify the size of the array:
void shuffle(int (&deck)[52])
{
deck[4] = 34;
}
If you don't know the size, or if you're limited to C, you need to use pointers.
The other (C++) option is to use a vector for the deck and pass a reference to the vector instead.
C doesn't have references (a), that's C++.
In C, arrays of X decay into a pointer to X[0] when passed to functions, so one way is to use:
void shuffle (int *pDeck) {
// use pDeck[something]
}
:
int deck[] = {0,1,2,3,4,5,6,7,8,9};
shuffle (deck);
I actually prefer that method to the void shuffle (int pDeck[]) variant since the former makes it absolutely clear that you're now dealing with a pointer rather than an array.
The reason this is important is because you lose the size information when you do that, so you may want to pass that in as well:
void shuffle (int *pDeck, size_t sz) {
// use pDeck[0 thru sz-1]
}
:
int deck[] = {0,1,2,3,4,5,6,7,8,9};
shuffle (deck, sizeof (deck) / sizeof (*deck));
(a): Although they are a very nice feature. I do hope that ISO considers them for the next C standard, since a large number of problems newcomers to the language have are involved with pointers, and references can hide the complexity of that very well.

Pointer / Array syntax (char **p, *p[n]) in C/C++

For pointers, I'm getting confused with declarations and function parameters on when to use char ** or char * or *array[n], etc. Like if a function takes a (*array[n]) parameter, do I pass it a **type?
I try using the Right-Left rule and know that p would be a pointer to a pointer to a char (char **p), and p is an array of n pointers (*p[n]), but someone said that *p[n] and **p are essentially equivalent. Is that true?
In the correct context (namely, arguments to a function), then the following declarations are equivalent:
int main(int argc, char *argv[]);
int main(int argc, char **argv);
int main(int argc, char *argv[12]); // Very aconventional!
Similar comments apply to the function definitions (which have a block enclosed in braces in place of the semi-colon).
In any other context, there are important differences between the notations. For example:
extern char *list1[];
extern char **list2;
extern char *list3[12];
The first says that somewhere there is an array of indeterminate size containing 'char *' values. The second says that somewhere - possibly here - there is a single value containing a pointer to a char pointer. The third says that somewhere - possibly here - there is an array of 12 character pointers.
However, all the three lists can be referenced in somewhat the same way - assuming that they actually have been defined and initialized.
list1[0][0] = '1';
list2[0][0] = '2';
list3[0][0] = '3';
Further, if they are passed into a function like this:
function(list1, list2, list3);
then the function can be declared as:
void function(char **list1, char **list2, char **list3);
The arrays (list1, list3) decay from the array to the pointer to the first element of the array; list2, of course, is already a pointer to a pointer.
One detail to note in a function such as:
void otherfunction(char *list[12])
{
...
}
The C compiler does not treat that declaration any differently from:
void otherfunction(char **list)
{
...
}
or
void otherfunction(char *list[])
{
...
}
In particular, it does no array bounds checking, and as far as the function is concerned, the 12 may as well be absent.
C99 introduces VLA (variable length array) types and also introduces a notation with 'static' and a size in the array bounds. You would need to read the standard to understand those fully.
Suffice to say in a function like the following the size of the array does matter, and is determined at run-time. With two-dimensional arrays in general, all the dimensions except the first need to be specified.
void vla_function(size_t m, int vla[m][m]);
Quoting from the standard (section 6.7.5.3):
void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);
(Note that the last declaration also specifies that the argument corresponding to a in any call to f must be a
non-null pointer to the first of at least three arrays of 5 doubles, which the others do not.)
Reading C declarators (that's the part of the variable with the * and []) is fairly nuanced. There are some websites with tips:
http://www.antlr.org/wiki/display/CS652/How+To+Read+C+Declarations
http://www.ericgiguere.com/articles/reading-c-declarations.html
A char** is a pointer to (possible multiple) pointer(s) to (possibly multiple) char(s). For example, it might be a pointer to a string pointer, or a pointer to an array of string pointers.
A char*[] is an array of pointers to char. When you have a function that takes this as a parameter, the C compiler makes it "decay" into a char**. This only happens to the first layer... so, taking a complicated example, char*[4][] becomes char*(*)[4]. Read the links above so you can understand what the heck that means.
Or you can do a (very sensible) thing and make a bunch of typedefs. I don't do this, but until you're good at reading declarators, it's a good idea.
typedef char * stringp;
void func(stringp array[]) { ... }
static stringp FOUR_STRINGS[4] = { ... };
If n==0 then they reference the same memory. Array indexing is basically a pointer plus an offset. *(p[n]) would be the same as **(p+n). You can see for yourself how simple this is in C, because array[4] and 4[array] will give you the same thing.

Resources