tags:

views:

508

answers:

5

I thought that an array variable cannot be changed in C, i.e. the base address of an array is unchangeable, but the following code contradicts my assumption :

#include <stdlib.h>
#include <stdio.h>

void changeArray(int **a)
{
   *a = malloc(sizeof(int));  
}

int main()
{
   int a[10];
   a[0] = 1;
   printf("%d\n",a[0]);
   changeArray(&a);
   printf("%d\n",a[0]);
}

This code prints :

1
6750576(some random value)

So clearly the base address of an array has been changed. How is it possible?

+2  A: 

In this code you have not changed the base address of the array. You have changed the value of the first element in the array.

JaredPar
dta
<code>[scameron@zuul ~]$ cat testitout.c#include <stdio.h>int main(int argc, char *argv[]){ char s[100]; printf("%p, %p\n", s,
smcameron
Arg. I can never remember the formatting code at this place.
smcameron
+1  A: 

&a does not result in int ** as you think it does. It evaluates to int *[10] just like a does in the following code snippet:

#include <stdio.h>

int main(void)
{
        int a[10];
        printf("%p %p\n", a, &a);
        return 0;
}

This prints for me:

0xbffff058 0xbffff058

So your changearray function is assigning the pointer being returned by your malloc to the first element of the array a in main.

Further, if you compiled with -Wall, you will see the a warning (as you should):

bad.c:14: warning: passing argument 1 of ‘changeArray’ from incompatible pointer type
freespace
nos
Sure, a fine point I have missed.
freespace
+1 for -Wall comment. Even better: -Wall -Werror !!
Peter K.
+13  A: 

The code is playing evil pointer games, without luck. You are passing a pointer having type int(*)[10] to a function that wants a pointer having type int**. The pointer you pass has as value the "base address" of the array.

So, when you dereference the int**, it thinks at that address it gets an int*, even though what it looks at is an int object. And it writes the address returned from malloc into that memory cell.

Back in main, you print out that cell. Note that it is the value of the array's first element, not the address of the array's first element. So what is printed is the integer value interpretation of the address you wrote in the called function.

What you do is undefined behavior: The function wants a int**, so you have to give it an int**,


Here is how i think you view the matter

  • You hear someone say that an array name is a constant pointer to its first element
  • You take the address of that pointer, cast away any const
  • You happily write some other address into the pointer, and hope that it doesn't crash
  • You use the array again, expecting it "overlays" now the uninitialized memory region that malloc created.

But that view is flawed. The first point is flawed the most, because an array name is not a constant pointer. If the first point were correct, your snippet would make a lot more sense, actually. But an array name will generate an address value that refers to its first element when you use it in an expression, except in very few cases (sizeof, address-of).

Because that address value is not generated when you use address-of, you will get a pointer to that array instead (which was exactly what you wrote with the address-of operator). Since it makes sense that an array and the first element of it has the same address, the address of an array happens to equal to the address of its first element. So what you actually did was writing into the first element, instead of writing into some pointer (that in reality isn't there).


Response to Comment

Consider what happens when the type of the array matters in practice (out of these experiments). You have an array whose elements are arrays themselves.

// array of 3 "array of 10 int"
int a[3][10];

So, by using a in an expression other than & and sizeof, you will get a pointer to the first element, int(*)[10]. This is crucial, because the following writes into an integer that's offset 2*sizeof(int)*10 bytes

a[2][0] = 1;
  // (a + 2) refers to a offset by 2*sizeof(int)*10 bytes

If a in that expression would give you an int**, then the compiler would have no idea where it should store the integer 1 correctly into, because any size information about the element type is lost. It could surely store the size somewhere in memory, but where? In the array, there is no space for that. And in addition, sizeof couldn't be able to give you a compile time result anymore.

Johannes Schaub - litb
+1, and madness? THIS IS... no I won't go there. ;-)
freespace
+1 Aye. This is like stabbing the program in the back with a rusty dagger.
Magnus Skog
Wait a minute, now I'm confused. What is a if not an `int*`? I know that the square brackets are really just dereferencing the first address plus the value in the brackets. I thought that `int a[10];` was just allocating an array of 10 ints on the stack and making a the pointer to the first one. So my question is, what is the difference between an `int(*)[10]` and an `int**`?
Sean Nyman
...Stupid asterisk notation for italics.
Sean Nyman
@Darth, edited. Hope it explains the matter somewhat?
Johannes Schaub - litb
Michael Burr
@Michael Burr, i changed the mad words to something else :) I'm a poor german without sense for english words =)
Johannes Schaub - litb
A: 

Which compiler did you use to compile the code? I tried using VC++ 2005, the code is illegal, which says "cannot convert parameter 1 from 'int (*__w64 )[10]' to 'int **'"

I am interested in how you compile the code and get the weired result.

using gcc on cygwin.
dta
You get warnings instead of errors if you compile it as C code instead of C++.
Michael Burr
A: 

The expression &a returns the address of the array a. That address gets passed into the chageArray() function as if it were a **int.

Inside the changeArray() function, the expression *a dereferences the pointer a single time, so the result of the expression is an lvalue correpsonding to the int variable at that address.

Outside of the changeArray() function you can think of the expression &a is the same as &a[0].

So what you've done in the changeArray() function is

*(&a[0]) = malloc(sizeof(int));

which is equivalent to

a[0] = malloc(sizeof(int));
Michael Burr