1、变量名在编译阶段被编译器替换成存储该变量值的内存空间的首地址,简单说:变量名代表存储该变量的内存空间首地址。
例如:定义一个int型变量
int a=10; //int型变量的用4个连续的字节存储,变量名a代表第一个字节的地址。
double b=12.1; //double型变量用8个连续的字节存储,变量名b代表第一个字节的地址。
2、数组名也不例外,也是代表存储该数组的内存空间的首地址。
例如:定义一个int型数组
int arr[3]={1,2,3}; //int型数组有3个元素,所以用12个连续的字节存储,数组名a代表第一个字节的首地址。
double d[3]={1.1,2.2,3.3}; //double型数组有3个元素,所以用24个连续的字节存储,数组名d代表第一个字节的首地址。
3、但是,数组有一点特殊的是,数组是一系列同类型元素的集合,C语言需要有一个指向单个元素的指针,方便操作各个元素。所以,编译器会根据上下文环境,自动的把数组名转换为数组首元素的地址,这样,就可以通过加减指针来指向数组中的各个元素了。
例如:
int arr[3]={1,2,3}; printf("arr[0]=%d ",*arr); printf("arr[1]=%d ",*(arr+1)); //注意arr会被编译器直接替换为数组首元素地址,所以是直接量,所以不能被改变,这里返回的是表达式*(arr+1)的值 printf("arr[2]=%d ",*(arr+2));
4、但是,有的时候我们又需要引用整个数组,比如用sizeof求数组的大小,或者求整个数组结构的首地址,不要担心,聪明的编译器会看出我们的需要。
例如:
int arr[3]={1,2,3}; printf("arr size=%zu",sizeof(arr)); //这里我们需要整个数组的大小,而不是一个指针的大小,聪明的编译器根据sizeof操作符知道我们要的是整个数组的大小。 int (*pa)[3]; //因为pa指针是指向一个数组类型,该类型有3个int型元素。编译器根据有没有&符号,判断你要的是数组首元素地址,还是数组的首地址。 pa=&arr; //虽然数组首元素地址和数组首地址的值是一样的,但类型是不同的,通俗的说大小是不同的,首元素地址指代的是一个元素的大小,首地址指代整个数组的大小。
5、用程序验证一下:
#include <stdio.h> int a[3]={1,2,3}; int main(){ printf("a=%p ",a); printf("a+1=%p ",a+1); printf("&a=%p ",&a); printf("&a+1=%p ",&a+1); return 0; }
输出结果为:
fly@noi:~$ ./t a=0x601038 a+1=0x60103c //a+1偏移了4个字节,说明a指向数组首元素的地址 &a=0x601038 &a+1=0x601044 //&a+1偏移了12个字节,说明a指向数组首地址
查看一下汇编版本是如果做的:
//gcc默认是at&t格式的汇编,还可以通过:gcc -S -masm=intel t.c > t.s,转换为intel格式。
gcc -S t.c > t.s vim t.s 输出: .file "t.c"
.globl a
.data
.align 8
.type a, @object
.size a, 12
a:
.long 1
.long 2
.long 3
.section .rodata
.LC0:
.string "a=%p "
.LC1:
.string "a+1=%p "
.LC2:
.string "&a=%p "
.LC3:
.string "&a+1=%p "
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $a, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $a+4, %eax //看到没,虽然代码里面没有给a加取地址的&符,但是在汇编里面仍然取了a的地址,只不过被编译器当作是首元素的地址
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
movl $a, %esi
movl $.LC2, %edi
movl $0, %eax
call printf
movl $a+12, %eax //哈哈,找到了,果然是加了12,表明编译器把&a当作数组的首地址,&a+1就是跨越了整个数组。
movq %rax, %rsi
movl $.LC3, %edi
movl $0, %eax
call printf
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
6、总结:由于数组在应用过程中,需要使用2个地址:首地址和首元素地址,编译器是根据数组名前面是否有&符进行判断的。可见,这里的&符已经不是普通意义的取地址符了,而是编译器判断的依据。由于首元素地址更加常用,所以,不加&的数组名定义为首元素地址。