龙空技术网

C语言揭秘:指针和数组

程序员火苗 219

前言:

此刻朋友们对“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语言数组和指针的区别