数组名是指向数组的指针吗
记得学习C语言的时候,有这样一个说法"数组名实际就是一个指向数组首地址的指针常量
",
那么对数组名取地址和对数组第1个元素取地址 必然得到相同结果
char ptest[20];
printf("ptest: %08x, ptest[0]: %08x, ptest[19]: %08x\n", (int)&ptest, (int)&ptest[0], (int)&ptest[19]);
// 本机输出 ptest: 0019ff18, ptest[0]: 0019ff18, ptest[19]: 0019ff2b
可以看到&ptest == &ptest[0]
那指向数组的指针跟数组名的使用一样吗
由于数组还可以通过new来得到,char* p = new char[20]
,p是一个指向数组的指针
,char p2[20]
p2也是一个指向数组的指针,既然p和p2都是指向数组的指针是否意味着已知&p2 == &p2[0]
一定有 &p == &p[0]
呢?
char * p = new char[20];
printf("p: %08x, p[0]: %08x, p[19]: %08x\n", (int)&p, (int)&p[0]);
char p2[20];
printf("p2: %08x, p2[0]: %08x, p2[19]: %08x\n", (int)&p2, (int)&p2[0], (int)&p2[19]);
// 本机输出
// p: 0019ff2c, p[0]: 007e49e0, p[19]: 004010f0
// p2: 0019ff18, p2[0]: 0019ff18, p2[19]: 0019ff2b
可以看到&p2 == &p2[0]
但是 &p != &p[0]
来看看汇编里都干了啥
为什么会这样呢?我们修改一下代码看一下汇编输出
这里涉及到两条汇编指令lea 和 mov 来看一下wikibook上的解释
- mov stands for move. Despite its name the mov instruction copies the src operand into the dest operand. After the operation both operands contain the same contents.
- lea stands for load effective address. The lea instruction calculates the address of the src operand and loads it into the dest operand.
- Load Effective Address calculates its src operand in the same way as the mov instruction does, but rather than loading the contents of that address into the dest operand, it loads the address itself. lea和mov用同样的方法计算操作数,但
lea将计算出的地址存入目标,mov将该地址上的内容存入目标
6: int main(int argc, char* argv[])
7: {
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,60h
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-60h]
0040101C mov ecx,18h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
8: char * p = new char[20];
00401028 push 14h
0040102A call operator new (00401090)
0040102F add esp,4
00401032 mov dword ptr [ebp-20h],eax
00401035 mov eax,dword ptr [ebp-20h]
00401038 mov dword ptr [ebp-4],eax // p本身是一个变量在栈上,它的地址&p是也一个数值,这个数值在ebp-4, new出来数组的地址在堆上,数组的地址需要放在变量本身占据的那块内存dword ptr [ebp-4]中
9: int tmp = (int)&p;
0040103B lea ecx,[ebp-4] // lea取的是 ebp-4的变量p的地址值
0040103E mov dword ptr [ebp-8],ecx
10: tmp = (int)&p[0];
00401041 mov edx,dword ptr [ebp-4] // mov取的是[ebp-4]指向存储单元中的内容, 这里编译器识别出p[0]是数组第1个元素,取的是变量p指向数组的地址
00401044 mov dword ptr [ebp-8],edx
11: p[0] = 3;
00401047 mov eax,dword ptr [ebp-4] // 在给p[0] 赋值时先得到真正的堆上的地址值,再将数据存入该地址空间
0040104A mov byte ptr [eax],3
12: //printf("p: %08x, p[0]: %08x, p[19]: %08x\n", (int)&p, (int)&p[0]);
13:
14: char p2[20];
15: tmp = (int)&p2;
0040104D lea ecx,[ebp-1Ch] // p2在栈上 p2本身的地址值在ebp-1ch 它本身的地址值就是数组的地址
00401050 mov dword ptr [ebp-8],ecx
16: tmp = (int)&p2[0];
00401053 lea edx,[ebp-1Ch] // 同样
00401056 mov dword ptr [ebp-8],edx
17: p2[0] = 3;
00401059 mov byte ptr [ebp-1Ch],3 // 在给p2[0]赋值时 直接使用地值值即可,注意与p[0]=3的区别
18: //printf("p2: %08x, p2[0]: %08x, p2[19]: %08x\n", (int)&p2, (int)&p2[0], (int)&p2[19]);
19: return 0;
0040105D xor eax,eax
20: }
结果
p和p2虽然都可以理解为指向数组的指针,但还是有细微区别的,它们的内存分别在栈上和堆上
通过上面分析可知,取数组地址时统一使用&p[0] 和 &p2[0]
这种方式是不会出错的。&p和&p2则有可能不是你想要的结果。
在栈上时p就代表数组首地址,&p也是数组首地址
在堆上的数组,p2代表指向数组首地址的指针,p2是在栈上,&p2 != 堆上数组的首地址