First, as a small matter of convenience, you may find it easier to initialize the arrays like this:
int x[2] = { 1, 2 };
int y[2] = { 3, 4 };
As a note, this is only legal in initialization. We can't do this:
int x[2];
x = { 1, 2 };
This is an array initialization. We're not "assigning" to an array because an array isn't an lvalue, but we can still initialize it. Note that any values we don't fill in will be initialized to zero:
int lots[100] = { 1 };
Now lots
is an array of 100 elements, lots[0]
is 1, and lots[1]
through lots[99]
are all 0. We guarantee the values because we initialized the array. If we just did:
int lots[100];
Then we've declared, but not initialized, our array, so it holds garbage like any other local variable. A common idiom is:
int lots[100] = { 0 };
To initialize our array to all zeroes - the one value we declare is 0, and the other ones are zeroed out automatically.
Second, to address your actual concern, let's look at doit()
:
int doit(int x[], int y[]) {
This is pretty basic - it declares a function that takes two arrays. But you can't pass arrays to a function in C, or return them from a function in C, so what does this really mean?
int doit(int *x, int *y) {
In C, all an "array" is (when passed to a function) is a pointer to the first element of that array. That's why arrays are zero indexed. To access the first element of an array, we dereference the pointer. To access the second element, we add sizeof(array type)
to the pointer, and dereference that:
x[0] == *x
x[1] == *(x + 1)
// and so on
A commonly noted product of this is the following:
x[2] == *(x + 2) == *(2 + x) == 2[x]
While not commonly used, it is still pretty cool. But don't use it in real code. That's decidedly not cool.
Anyway, so the addresses of our arrays (well, pointers to the first elements of our arrays) are passed to our doit()
function. What happens next:
x = y;
This is saying "Tell our local x
pointer to point to y
. It doesn't change the original x
array, because we're assigning to the pointer, not to the array (which we can't do). So we end up basically with two names, x
and y
, for the same array. Any changes to x
or y
will be reflected in the array passed into y
.
x[0] = 5;
y[2] = 10;
This is pretty basic. We set the first element of our array to 5 and the third element to 10. No problems here, assuming the array passed in for y
has at least three elements (cue ominous music).
}
Actually, this is one of the bigger problems in doit()
. How did we declare doit()
?
int doit(int x[], int y[])
So doit()
returns an int
, but we have no return
statement! While some compilers may accept this in varying degrees, it's much better to either return a value, or change the return type of doit()
to void
(which I suspect is what you want) so that it doesn't return anything. If your function returns void
, you can omit the return
statement at the end, or you could explicitly say return;
with no arguments, since you're returning nothing. (Don't worry, this monster is almost finished.)
The biggest problem here, as you well know, is that we can't guarantee that the arrays we pass our function will have three elements. We can't even guarantee they'll be arrays. We can call it like this:
int i = 10;
doit(&i, &i);
And it's legal. The C language will not check to make sure the arrays you pass a function are big enough, nor will it give you any built-in facilities for checking the size of the array. This doesn't work:
size_t array_size(int a[]) {
return sizeof(a) / sizeof(a[0]);
}
It won't work because it's rewritten at the compiler level as this:
size_t array_size(int *a) {
return sizeof(a) / sizeof(a[0]);
}
And sizeof(int *)
is not the size of the array, but the size of the pointer to the array (and the pointer is just a numeric address in memory, usually a computer word). So while it's legal to modify y[2]
in your doit()
function, the array you passed in main()
doesn't have enough elements, so we get undefined behavior. The compiler can do anything - it can overwrite x
, or overwrite some other local variable, or overwrite your code data, or do anything it wants to (the classic example is causing demons to fly out of your nose) and still be a valid C compiler. That's the danger of C - it won't check to make sure you play by the rules. The reason for this is that it's meant to be a fast language, capable of producing highly efficient code, and checking whether or not you play by the rules makes your program slower. So if you want to make sure you play by the rules, you have to check the rules yourself:
void doit(int y[], size_t len) {
y[0] = 5;
if(len > 2)
y[2] = 10;
else
y[len] = 10;
}
Then call it:
#define SIZE 2
int y[SIZE] = { 3, 4 };
doit(y, SIZE);
Now we can call doit()
safely, even on small arrays. This pattern of passing an array length as a separate argument to a function is very common in C, and is found in most C standard library functions dealing with arrays and pointers.