I read somewhere that the following arrays can be passed to the following functions in such a manner, as shown below, however I do not understand what elements within the ar
What you can percieve as arrays in C are not always arrays in terms of language concepts.
C is an language from the 70's. It's very close to machine implementation compared with its more abstract offsprings. So you must take implementation into account if you want to understand what lies beyond confusingly similar syntactic elements.
Both pointers and arrays can be accessed through (square) bracket notation.
Bracket notation is undoubtely useful, but it is the root of all evil as far as confusions between pointers and arrays is concerned.
f[i]
will "work" for pointers and arrays as well, although the underlying mechanisms will differ, as we will see.
Let's start with variable declarations.
float * f
simply tells the compiler that the symbol f
will someday reference an unknown number of floats.
f
is not initialized. It is up to you to decide where the actual data will be and to set f
to point to them.
Keep in mind that when you add/substract a value to a pointer, the unit is the size of the pointed type.
float * f;
float * f3 = f+3; // internal value: f + 3 * sizeof (float)
// these two statements are identical
*f3 = 1;
*(f+3) = 1;
Since writing *(f+i)
is awkward when you want to reference contiguous data from a pointer, the bracket notation can be used:
f[3] = 1; // equivalent to *(f+3) = 1;
Regardless of the notation used, the address of f[3] is computed like that:
@f[
3] = f +
3* sizeof (float)
You could consider f
functionally as a (dynamic) array, but as C sees it, it's still a pointer, referenced through a syntax that makes it look like an array.
float f[10]
still tells the compiler that f
will reference some floats, but it also
f
is an automatic local variablef
is a global or static variablef
as a constant pointer to the first of these float valuesEven though array creation syntax might be confusing, an array will always have a fixed size known at compile time.
For instance, float f[] = {2,4,8}
declares an array of length 3, equivalent to float f[
3] = {2,4,8}
. The dimension can be omitted for convenience: the length mirrors the number of initializers without forcing the programmer to repeat it explicitey.
Unfortunately, the []
notation can also refer to pointers in some other circumstances (more on that later).
Bracket notation is the most natural way of accessing array contents.
When you reference an array, the compiler knows it's an array. It can then access the data based on the array first element, like that:
@f[
3] = f +
3* sizeof (float)
In case of single-dimensional arrays (but in that case only!), you can see that the address computation is exactly the same as for a pointer.
Since an array is also considered a (constant) pointer, you can use an array to initialize a pointer, thought the reverse is obviously false (since an array is a constant pointer and as such its value cannot be changed).
void test (void)
{
float* f1;
float f2[10];
float f3[]; // <-- compiler error : dimension not known
float f4[] = {5,7}; // creates float f4[2] with f4[0]=5 and f4[1]=7
f1[3] = 1234; // <--- write to a random memory location. You're in trouble
f2[3] = 5678; // write into the space reserved by the compiler
// obtain 10 floats from the heap and set f1 to point to them
f1 = (float *) calloc (10, sizeof(float));
f1[3] = 1234; // write into the space reserved by you
// make f1 an alias of f2 (f1 will point to the same data as f2)
f1 = f2; // f2 is a constant pointer to the array data
printf ("%g", f1[3]); // will print "5678", as set through f2
// f2 cannot be changed
f2 = f1; // <-- compiler error : incompatible types ‘float[10]’ / ‘float *’
}
let's extend our example to the two-dimensional case:
float f2[3][10]; // 2d array of floats
float ** f1; // pointer to pointer to float
f1 = f2; // <-- the compiler should not allow that, but it does!
f2[2][5] = 1234; // set some array value
printf ("%g\n", f2[2][5]); // no problem accessing it
printf ("%g\n",f1[2][5]); // bang you're dead
let's see what happened here
when you declare float f2[3][10]
, the compiler allocates the 30 required floats as a contiguous block. The first 10 floats represent f[0], the next ten f[1], etc.
When you write f2[
2][
5]
, the compiler still knows f
is an array, so it can compute the effective address of the required float like this:
@f2[
2][
5] = f + (
2* 10 +
5) * sizeof (float)
You can also access pointers through multiple brackets, provided the pointer has the proper number of reference levels:
When referencing the pointer, the compiler simply applies pointer arithmetics in succession:
float h = f1[2][5];
is equivalent to:
float * g = f1[2]; // equivalent to g = *(f1+2)
float h = g[5]; // equivalent to h = *(g +5)
f1[
2][
5]
is handled by the compiler as *(*(f1+
2)+
5)
.
The final address will be computed like this:
@f1[
2][
5] = *(f +
2* sizeof (float *)) +
5* sizeof (float)
Beyond the same bracket notation lie two very different implementations.
Clearly, when trying to access f2
data through f1
, the results will be catastrophic.
The compiler will get the 3rd float from f2[2]
, consider it as a pointer, add 20 to that and try to reference the resulting address.
If you write some value through this kind of wrongly initialized pointer, consider yourself lucky if you get an access violation instead of silently corrupting some random four bytes of memory.
Unfortunately, even though the underlying data structure cannot be accessed properly unless the compiler is aware that f2
is an array, f2
is still considered a constant float**
pointer.
In an ideal world it should not, but in C (alas!), it is.
It means you can assign a pointer to an array without the compiler complaining about it, even though the result makes no sense.
Both arrays and pointers can be passed as parameters to functions.
However, to avoid such catastrophic misinterpretations as in the previous example, you must let the compiler know whether what you are passing to the function is an array or a pointer.
Here again, due to the compiler considering an array as a constant pointer, you will be allowed to do silly things like declaring an array and passing it to a function like a pointer.
To make things worse, the syntax of function parameter declaration allows to use brackets in a way that makes confusion between arrays and pointers even more likely.
void f (float f1[]);
is handled exactly as
void f (float * f1);
even though the variable declaration
float f1[];
will yield an error instead of considering it as an alternate way of declaring float * f
.
You could say that the []
notation is allowed to designate pointers, but only in function parameters.
Why it has not be allowed as a variable declaration might be open to debate (among other things, it would be ambiguous with the float f[] = { ... }
initialized array declaration syntax), but the net result is that function parameter decalaration introduce a notation that adds another layer of confusion.
For instance, the famous argv
parameter can be declared any odd way:
int main (int argc, char ** argv)
int main (int argc, char * argv[])
int main (int argc, char argv[][])
On the other hand, provided you are fully aware of the difference between pointers and arrays, the empty brackets are somewhat more convenient than the pointer notation, especially in that case:
void fun (float f[][10]); // pointer to arrays of 10 floats
the equivalent pointer syntax forces you to use brackets:
void fun (float (* f)[10]);
which you can't avoid when declaring such a variable:
float (* f)[10]; // pointer to array of 10 floats
float f[][10]; // <-- compiler error : array dimension not known
As far as function prototypes are concerned, you have the choice between syntactic variants, but if the variable you pass to the function doesn't match the prototype, it will all end in tears.
float ** var1; // pointer to pointer to float
float * var2[10]; // array of pointers to float
float (* var3)[10]; // pointer to array of floats (mind the brackets!)
float var4[10][10]; // array of arrays of floats (2d array of floats)
// using empty brackets notation
void fun1 (float f[ ][ ]);
void fun2 (float f[10][ ]);
void fun3 (float f[ ][10]);
void fun4 (float f[10][10]);
// using same syntax as for variables declaration
void fun1 (float ** f);
void fun2 (float * f[10]);
void fun3 (float (* f)[10]); // <-- [] notation (arguably) easier to read
void fun4 (float f[10][10]); // must always use square brackets in that case
// even more choice for multiple level pointers
void fun1 (float * f[]);
// any funI (varJ) call with I != J will end up in tears
It is of course a matter of personal taste, but I would recomend the use of typedef
as a way of getting a bit more abstraction and limit the use of C syntactic oddities to a minimum.
// type definition
typedef float (* tWeirdElement)[10];
typedef tWeirdElement (* tWeirdo)[10]; // pointer to arrays of 10 pointers
// to arrays of 10 floats
// variable declaration
tWeirdo weirdo;
// parameter declaration
void do_some_weird_things (tWeirdo weirdo);
First clear your confusion about arrays and pointers. Always remember that, arrays are not pointers.
Among all of your declarations, only two of them
int array[NROWS][NCOLUMNS];
int (*array4)[NCOLUMNS];
are arrays. Rest of them are pointers, not arrays.
Isn't
array1
andarray2
an array of pointers?
No. Never. array1
and array2
are int **
type, i.e, are of type pointer to pointer to integer.
So when you pass them to the
f3
, all the pointers get passed?
No. I explained it above that array1
and array2
are not array of pointers.
And about
array2
when passed tof2
,f2
has a normal pointer in formal arguments, butarray2
is an array of pointers, so how would you access individual rows and columns when passing array of pointers tof2
?
f2
expects a pointer to int
as its first argument. *array2
is a pointer to int
. Hence in the call
f2(*array2, nrows, ncolumns);
a pointer to int
is passed to f2
as its first argument, not an array of pointers.
And how would u access individual rows and columns when passing
array4
which is a pointer to a series of 1D arrays to functionf2
?
Since you are passing a pointer to int
type argument to f2
, you can access only row.