前言:
此刻朋友们对“c语言数组和指针的区别”大体比较关切,咱们都需要分析一些“c语言数组和指针的区别”的相关内容。那么小编同时在网络上搜集了一些对于“c语言数组和指针的区别””的相关内容,希望小伙伴们能喜欢,小伙伴们快快来了解一下吧!在《C语言揭秘:各种数据类型》中已经介绍了指针和数组,但在实际编码中指针和数组容易被错误使用,本文就有必要从不同场景分析它们互通性和独特性。
访问指针所指变量
C语言中,指针是指向其他变量的变量。比如:
int n = 10;int * pn;int main(){ pn = &n;}
pn就是一个指针变量,它指向变量n,pn的值就是n的地址,这个我们已经知道了。
我们访问n,可以用:
int n1 = 0xbabebeaf;int n2 = 0xdeaddeaf;int *pn1;int *pn2;int main(){ pn1 = &n1; pn2 = &n2; *pn1 = 5; // 解引用方式 pn2[0] = 8; // 数组下标方式}
得到:
11行是正常的解引用方式,好理解。
重点注意第12行,采用了数组下标的方式给n2赋值,可以看到与上图中0xa0000004中的值0x8匹配,说明赋值成功。即这种情况下,解引用和数组下标方式通用。
访问数组元素
int arr1[2] = {1,2};int arr2[2] = {3,4};int main(){ *arr1 = 5; arr2[0] = 8;}
得到结果:
可以看到6、7行都可以成功给数组中第一个元素赋值。因此2种方式通用。
指针的地址
指针作为一个变量也可以取其地址,那就是二重指针,比如:
int n1 = 0xbabebeaf;int *pn1;int **ppn1; // 二重指针int main(){ n1 = 5; // n1的值为5 pn1 = &n1; // pn1的地址为n1的地址0xa0000000 ppn1 = &pn1; //取指针变量pn1的地址赋给ppn1,即ppn1的值为0xa0000008}
得到如图:
可以看到,如图所示与代码7-9行的注释一致。也就是指针的定义。没问题。
数组的地址
通常我们不会用到数组的地址,但是我们这里为了与指针地址进行对比,我们有如下代码:
int arr[2] = {1,2};int arr_val;int **parr;int main(){ arr_val = (int)arr; parr = (int**)&arr;}
得到:
我们看到:
因arr_val=(int)var; 且arr_val为0xa0000000,即使说arr的值就是0xa0000000,也就是arr的第0个元素的地址。因parr = (int**)&arr; 且parr为0xa0000000,即使说arr的地址就是0xa0000000,也就是arr的第0个元素的地址。
总结起来就是:数组变量的值==数组变量的地址==数组变量第0个元素的地址。
从这里可以看到数组与指针的不同:指针本身就是一个变量,指针的地址就是一个变量的地址,肯定是与这个指针变量本身的值是不相同的;但数组变量只是它第0个元素的别名,数组的地址就是它第0个元素的地址,表示用这个变量可以依次从0开始访问后面的各个元素而已。
我们再从汇编的角度分析这个不同:
对于数组:
00000020 <main>: 20: e52db004 push {fp} ; (str fp, [sp, #-4]!) 24: e28db000 add fp, sp, #0 28: e59f2024 ldr r2, [pc, #36] ; 54 <main+0x34> 将0xa0000000给r2 2c: e59f3024 ldr r3, [pc, #36] ; 58 <main+0x38> 将0xa0000008的值给r3 30: e5832000 str r2, [r3] ; 将0xa0000000放到parr所在地址0xa0000008 34: e59f3020 ldr r3, [pc, #32] ; 5c <main+0x3c> 将0xa000000c的值给r3 38: e59f2014 ldr r2, [pc, #20] ; 54 <main+0x34> 将0xa0000000给r2 3c: e5832000 str r2, [r3] ; 将0xa0000000放到arr_val所在地址0xa000000c 40: e3a03000 mov r3, #0 44: e1a00003 mov r0, r3 48: e28bd000 add sp, fp, #0 4c: e49db004 pop {fp} ; (ldr fp, [sp], #4) 50: e12fff1e bx lr 54: a0000000 .word 0xa0000000 ; 变量arr所在地址 58: a000000c .word 0xa000000c ; parr所在地址 5c: a0000008 .word 0xa0000008 ; 变量arr_val所在地址
从上面反编译的注释中可以看到,对于数组来说&arr的值与arr的值的处理方式完全相同。
对于指针,反汇编:
00000020 <main>: 20: e52db004 push {fp} ; (str fp, [sp, #-4]!) 24: e28db000 add fp, sp, #0 28: e59f3030 ldr r3, [pc, #48] ; 60 <main+0x40> r3 = 0xa0000000 2c: e3a02005 mov r2, #5 ; r2 = 5 30: e5832000 str r2, [r3] ; 0xa0000000处写入5 34: e59f3028 ldr r3, [pc, #40] ; 64 <main+0x44> r3= 0xa0000008 38: e59f2020 ldr r2, [pc, #32] ; 60 <main+0x40> r2 = 0xa0000000 3c: e5832000 str r2, [r3] ; 0xa0000008处写入 0xa0000000,即pn1=&n1 40: e59f3020 ldr r3, [pc, #32] ; 68 <main+0x48> r3 = 0xa0000004 44: e59f2018 ldr r2, [pc, #24] ; 64 <main+0x44> r2= 0xa0000008 48: e5832000 str r2, [r3] ; 0xa0000004处写入0xa0000008,即ppn1 = &pn1 4c: e3a03000 mov r3, #0 50: e1a00003 mov r0, r3 54: e28bd000 add sp, fp, #0 58: e49db004 pop {fp} ; (ldr fp, [sp], #4) 5c: e12fff1e bx lr 60: a0000000 .word 0xa0000000 ; n1 所在地址 64: a0000008 .word 0xa0000008 ; pn1所在地址 68: a0000004 .word 0xa0000004 ; ppn1所在地址
上面的注释完整解释了一级指针和二级指针的使用方式,完美符合指针的定义。
指针和数组作为函数参数指针作为函数参数
比如下面函数inc实现的功能是将pn所指向的整型数加一:
void inc(int *pn){ *pn = *pn+1;}int n = 5;int main(){ inc(&n);}
反汇编后:
00000020 <inc>: 20: e52db004 push {fp} ; (str fp, [sp, #-4]!) 24: e28db000 add fp, sp, #0 28: e24dd00c sub sp, sp, #12 2c: e50b0008 str r0, [fp, #-8] ; pn = r0 = 0xa0000000 30: e51b3008 ldr r3, [fp, #-8] ; r3 = pn = 0xa0000000 34: e5933000 ldr r3, [r3] ; r3 = RAM[0xa0000000] = 5 38: e2832001 add r2, r3, #1 ; r2 = r3+1 = 6 3c: e51b3008 ldr r3, [fp, #-8] ; r3 = pn = 0xa0000000 40: e5832000 str r2, [r3] ; RAM[0xa0000000] = r2 = 6,即n=6 44: e1a00000 nop ; (mov r0, r0) 48: e28bd000 add sp, fp, #0 4c: e49db004 pop {fp} ; (ldr fp, [sp], #4) 50: e12fff1e bx lr00000054 <main>: 54: e92d4800 push {fp, lr} 58: e28db004 add fp, sp, #4 5c: e59f0014 ldr r0, [pc, #20] ; 78 <main+0x24> r0 = &n = 0xa0000000 60: ebffffee bl 20 <inc> ; 跳转到inc函数 64: e3a03000 mov r3, #0 68: e1a00003 mov r0, r3 6c: e24bd004 sub sp, fp, #4 70: e8bd4800 pop {fp, lr} 74: e12fff1e bx lr 78: a0000000 .word 0xa0000000 ; &n = 0xa0000000
得到结果:
详细分析见上面汇编的注释。我们看到,在inc中做+1操作是直接在pn变量所指向的内存0xa0000000上操作,没有在内存做任何拷贝。
可以得到结论:
将指针作为函数参数时,就是把实参(一个地址值&n)传给形参(pn),操作*pn = *pn+1;就是将pn所指向的变量n,加1。
将指针作为函数参数的场景包括:
通过调用函数修改实参本身的值;返回大于一个参数是;为了更好的性能,需要零拷贝;参数是一个结构体,传值繁杂且低效;参数时一个函数指针,作为回调函数时。数组作为函数参数
还是刚才的功能,参数换成数组形式:
void inc(int pn[]){ pn[0] = pn[0]+1;}int n = 5;int main(){ inc(&n);}
从C语言的角度分析,前面有结论,数组名就是它的第0个元素的别名,那在该代码中,将&n传给inc,那pn的第0个元素就是n,因此:pn[0] = pn[0]+1; 就是n=n+1; 所以结果同指针情况。
生成反汇编同指针情况完全一样,直接参考上面汇编。
所以得到结论:
指针和数组作为函数参数是等价的。
标签: #c语言数组和指针的区别