数组与指针,究竟有什么关联?什么情况下等同,什么时候不等同? 老问题,新收获
前两天看《C专家编程》,结果硬是卡在数组与指针这块好久,原本以为自己已经掌握的东西,原来还是没有掌握精髓
抛出两个问题,然后我们在解决这两个问题的过程中review指针与数组:
问题一:
char arr[] = "abcdefg";
char *ptr = "abcdefg";
我们知道,arr[i]与ptr[i]都是正确的,但两者又有什么根本的差异呢?如果是char (*pa)[10]与char **pp,那么pa[1][2]与pp[1][2]有什么区别呢?
问题二:
qsort在针对指针数组与二维数组排序时的区别?
----------------------------------------------------------------------------------------------------------
问题一:
首先,要明确一点,编译器为每个标识符分配一个地址,这个地址在编译阶段可知,相反,存储在标识符中的值只有在运行时才可知
对于数组,有 &arr = arr = &(arr[0])
对于指针,有 &ptr != ptr = &(ptr[0])
对于编译器,它所知道的是&arr与&ptr,这两个标识符的地址
基于上述三条(前两条,写个简单的程序即可测试),即可得出结论: 虽然arr[i]与ptr[i]都能正确访问某个字符(事实上,都会被编译器改写成*(arr+i)或者*(ptr+i)的形式),但是两者生成的汇编代码是不同的
验证:
C语言代码:
char arr[] = "abcdefg";
char *ptr = "abcdefg";
void
difference()
{
char a = arr[3];
char b = ptr[3];
}
汇编代码(gcc -S test.c)
difference:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
/* char a = arr[3] */
movzbl arr+3, %eax /* 取地址(arr+3)处的值,放入寄存器 */
movb %al, -1(%ebp)
/* char b = ptr[3] */
movl ptr, %eax /* 取ptr的值(Mem[ptr]),放入寄存器 */
addl $3, %eax
movzbl (%eax), %eax /* 将寄存器%eax的值作为地址,取该地址的值放入寄存器 */
movb %al, -2(%ebp)
leave
ret
略微对上述汇编代码进行了注释,可以发现两者的差异,指针跟数组相比,前者多了“一层间接引用”
(Note: 汇编寻址,例如,$Imm是立即数,Imm是绝对寻址(=Mem[Imm]),Ea是寄存器寻址,(Ea)则是间接寻址(=Mem[Register[Ea]]);可参考《深入理解计算机系统》第3章)
如果是char (*pa)[10]与char **pp,pa是指向数组的指针,而pp是指向指针的指针,这里有点类似于之前的讨论
需要明白的就是: pa是指向数组的指针,保存的就是数组的地址;数组的地址等于数组首元素的地址;pa[i]等同于一元数组名称,就是指向数组首元素的指针,也保存的是数组首元素的地址,因此有: pa+i(指向数组的指针) = pa[i] = &(pa[i][0])
表达式pa[1][2]与pp[1][2]所产生的汇编代码也是不同的
(Note: 指向数组的指针p与数组名a(指向数组首元素的指针),两者的值相同,但含义不一样,例如p+1与a+1就完全不同)
验证:
C语言代码:
void
array_print(char (*pa)[10])
{
char c = pa[1][2];
}
void
pointer_print(char **pp)
{
char c = pp[1][2];
}
汇编代码:
array_print:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax /* 将pa的值(Mem[pa])放入寄存器 */
addl $10, %eax
movzbl 2(%eax), %eax
movb %al, -1(%ebp)
leave
ret
pointer_print:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax /* 将pp的值放入寄存器 */ addl $4, %eax
movl (%eax), %eax
addl $2, %eax
movzbl (%eax), %eax
movb %al, -1(%ebp)
leave
ret
问题二:
有了前面的讲解,这里直接上代码
#include "basic.h"
char *strings[] = { "zhao",
"pan",
"anan",
NULL };
char arr[][5] = { "zhao",
"pan",
"anan" };
void
test_a2_addr()
{
printf("Addr Testing\n");
printf("addr(arr+1): %p\n", arr+1);
printf("addr(arr[1]): %p\n", arr[1]);
printf("addr(arr[1][0]): %p\n", &arr[1][0]);
}
int
compare_pp(const void *arg1, const void *arg2)
{
return strcmp(*(char **)arg1, *(char **)arg2);
}
int
compare_a2(const void *arg1, const void *arg2)
{
return strcmp(*(char (*)[5])arg1, *(char (*)[5])arg2);
//return strcmp((char *)arg1, (char *)arg2); //ok,too
}
void
test_pp()
{
qsort(strings, 3, sizeof(char *), compare_pp);
printf("\nResult of qsort for POINTER-ARRAY\n");
char **s = strings;
while(*s != NULL) {
printf("%s\n", *s);
++s;
}
}
void
test_a2()
{
qsort(arr, 3, sizeof(char)*5, compare_a2);
printf("\nResult of qsort for TWO-ARRAY\n");
int i;
for(i=0; i<3; ++i)
printf("%s\n", arr[i]);
}
int
main(int argc, char **argv)
{
test_a2_addr();
test_pp();
test_a2();
}