tags:

views:

154

answers:

4

In the following code, when the line doit(x,y) is executed, what is passed to the pointer? The addresses of x and y or the values of x and y?

#include <stdio.h>

int doit(int x[], int y[]) {
   x = y;
   x[0] = 5;
   y[2] = 10;
}

int main(void) {
   int x[2];
   int y[2];

   x[0] = 1;
   x[1] = 2;
   y[0] = 3;
   y[1] = 4;

   doit(x, y);
   printf("%d %d %d %d", x[0], x[1], y[0], y[1]);
}
A: 

The addresses of arrays are passed to the doit() function, but what does "passed to the pointer" mean?

Michael Krelin - hacker
I can understand why I don't get upvotes, but why downvote?
Michael Krelin - hacker
Considering how much more reputation points you have, I feel silly explaining habits of this website, but I think these requests for clarification are expected to be expressed as comments on the question, not as answers (and you definitely have the minimum reputation for commenting).
Pascal Cuoq
Pascal, did you not notice that there was an answer preceding the question?
Michael Krelin - hacker
+2  A: 

Adress of the first member of the array.

We can check this using a debugger, for example, gdb.

Starting program: /tmp/doit 

Breakpoint 2, main () at doit.c:11
11  int x[]={1,2,4,5};
(gdb) n
12  int y[]={11,12,14,15};
(gdb) print x
$1 = {1, 2, 4, 5}
(gdb) print y
**$2 = {134520820, -1076989448, 134513312, -1207230476}**
(gdb) print &x[0]   <--- PRINT ADDRESS OF FIRST ELEMENT OF X.***
***$3 = (int *) 0xbfce71f4
(gdb) print &y[0]   <--- PRINT ADDRESS OF FIRST ELEMENT OF Y.***
$4 = (int *) 0xbfce71e4
(gdb) n
14  doit(x,y);
(gdb) step
//FUNCTION CALL IS DISPLAYED HERE. 
Breakpoint 1, ***doit (x=0xbfce71f4, y=0xbfce71e4)*** at doit.c:7
7 }
(gdb)

Values of x and y as passed to doit are shown here:

Breakpoint 1, ***doit (x=0xbfce71f4, y=0xbfce71e4)*** at doit.c:7

x is 0xbfce71f4. Thats the address of the first element of the array X.

y is 0xbfce71e4. Thats the address of the first element of the array Y.

Additionally, think about this.

arrays can't be assigned in C. What I mean, is that x=y will yield a compilation error. (something like : error: incompatible types in assignment). Consequently, if each parameter received an array, instead of the address of its first element, your code wouldn't compile.

Tom
+2  A: 

When arrays are passed as arguments, they always 'degrade' to simple pointers. So the prototype:

int doit(int x[], int y[]) ;

is equivalent to

int doit( int* x, int* y ) ;

I prefer the second as it is clear what is really happening, the first promises something that cannot be delivered. It serves perhaps as an indication that what is expected is a pointer to an array rather a pointer to a single object, but it has no effect on actual code generation. Often such functions have additional arguments to specify the size of teh array being passed.

The difference between an array and a pointer is simply that an array contains size information. So for example in the following:

void fn( int x1[], int* x2 )
{
    printf( "sizeof(x1) = %u\n", sizeof( x1 ) ) ;
    printf( "sizeof(x2) = %u\n", sizeof( x2 ) ) ;
}

int main()
{
    int x[10] ;
    int* y = x ;
    printf( "sizeof(x) = %u\n", sizeof( x ) ) ;
    fn( x, y ) ;
}

Will output (on 32bit platform):

sizeof(x) = 40
sizeof(x1) = 4
sizeof(x2) = 4

yet x, x1 and x2 all refer to the same array.

Clifford
Don't you mean 'sizeof(x) = 40'? (And 'paltform' could be fixed too).
Jonathan Leffler
While we're at it, why are you declaring `fn()` to take two arguments, but only passing it one?
Chris Lutz
Thanks. Fixed those.
Clifford
+4  A: 

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.

Chris Lutz
Jesus, that is a massive post. Now I need a nap.
Chris Lutz
-1 "In C, all an "array" is is a pointer to the first element of that array." No they are not. An array is an array, a pointer is a pointer. I'll come back with a very good example by litb.
Tom
When you pass an array to a function, that's all an array is. But you're right, I should have clarified that.
Chris Lutz
:) I'll take my downvote then
Tom
isn't the only difference between the two how they are initialized? When you work with an array with the array syntax it all translates to pointer arithmetic.
Carson Myers
@Carson - Arrays are also constant, and not lvalues. `values = func()` is valid for pointers, but not arrays.
Chris Lutz