深度剖析C数组名

微信公众号:编程笔记本

点击上方蓝字关注我,我们一起学编程
欢迎小伙伴们分享、转载、私信、赞赏

今天我们来讨论一个 C 中很有趣的问题:a 与 &a

当然啦,此处的 a 代表的是一个数组的数组名,因为我们每个人都写过这样的代码:int a[10]; 所以 a 在很多时候就成了数组名的“代言人”了。好了,闲话少叙,让我们直接正面刚!

先来看一个例子:

#include <stdio.h>

int main()
{
    int a[3] = {1, 2, 3};
    int *p = (int *)(&a + 1);
    
    printf("%d, %d\n", *(a + 1), *(p - 1));
    
    return 0;
}

大家可以先停一下,自己琢磨一下程序到底会输出什么?有了自己的答案以后再接着往下阅读。

怎么样了?聪明的你一定很快就有了答案吧!那我就先把运行的结果贴出来,大家先比对一下自己的答案和标准答案的异同,然后带着疑问来看详细的分析过程。

jincheng@haofan:~$ gcc -o test test.c
jincheng@haofan:~$ ./test
2, 3
jincheng@haofan:~$

不管你有没有做对,下面都跟着我一起好好分析一下过程吧。

首先,我们要明确两点:

  • **数组的数组名表示的是数组首元素的首地址。**这么说可能有一点拗口,举个例子说明一下。针对本段代码中,数组 a 的元素类型是 int 型,而一个 int 型数据占用 4 个字节(在 32 机器中)的内存空间,此时,数组名 a 表示的是数组首元素 a[0](1)的首地址(1 占用 4 个字节,指向其第一个字节)。
  • **对指针进行 +1 运算,得到的是下一个元素的地址,而不是下一个字节的地址。**也就是说,假如指针 ptr 指向 int 型数组的第 n 个元素,那么对 ptr 进行 +1 操作后,ptr 指向数组的第 n+1 个元素,即 ptr 跨过了 4 个字节的空间。

明确了这两点以后,我们再来接着往下分析。

在本例中,a 是一个 int 类型一维数组,数组中有 3 个元素;p 是一个 int* 型的指针。在 int *p = (int *)(&a + 1); 中,**&a 是取数组的首地址(而非数组首元素的首地址),&a+1 指向的是下一个数组的首地址,表示的是 &a+sizeof(a) 的值。*再将计算出来的地址值强制转换成 int 类型赋值给 p 。
a 和 &a 的值是一样的,但是表示的意思不同。**a 表示的是数组首元素(a[0])的首地址,而 &a 表示的是数组的首地址。**所以,a+1 是数组下一个元素(a[1])的首地址,&a+1 是下一个数组的地址。

分析到这,结果自然就出来了。*(a+1) 表示的是数组第二个元素,即 2 ;p 指向的是第二个数组的首元素的首地址,即 a[3]、a[4]、a[5] 中的首元素 a[3]的首地址,而指针 p 被强制转换成 int* 型,所以对 p 进行 -1 运算,会使得 p 向前移动 4 个字节的位置,即由 a[3] 移动到了 a[2]。所以,*(p-1) 为 3。


再来看一个例子:

#include <stdio.h>

int main()
{
    char a[3] = {'a', 'b', 'c'};
    
    char (*p1)[3] = &a;    // <1>
    char (*p2)[3] = a;     // <2>
    
    printf("p1 = %d, p1 + 1 = %d\n", p1, p1 + 1);
    printf("p2 = %d, p2 + 1 = %d\n", p2, p2 + 1);
    
    return 0;
}

显然,p1 和 p2 都是指向数组的指针,但是哪种用法是正确的呢?

<1> :&a 表示的是整个数组的首地址,而 p1 表示的是指向整个数组的指针,因此,= 两边的数据类型相同,所以初始化正确。
<2> :a 表示的是数组首元素的首地址,而 p2 表示的是指向整个数组的指针,因此,= 两边的数据类型不同,但所幸强制转换能够进行,又因为 a 与 &a 的值是相同的(表示的意义不同),所以 p2 与 p1 都是成功的初始化,但 p2 会伴随有警告信息:initialization from incompatible pointer type 。

所以,虽然 <2> 方法也能够成功运行,但强烈建议不要这么做。

分析到这里,运行结果自然而然地就得出了:**p1 指向长度为 3 的 char 型数组,则 p1+1 指向的是下一个长度为 3 的 char 型数组。**因此,两者差值为 3*sizeof(char) ,即差值为 3 。p2 类似。

jincheng@haofan:~$ gcc -o test test.c
test.c: In function ‘main’:
test.c:8:21: warning: initialization from incompatible pointer type
     char (*p2)[3] = a;

jincheng@haofan:~$ ./test
p1 = -724999163, p1 + 1 = -724999160
p2 = -724999163, p2 + 1 = -724999160
jincheng@haofan:~$

下面两种情况,分析思路类似,这里直接贴出运行结果:

(1)

#include <stdio.h>

int main()
{
    char a[3] = {'a', 'b', 'c'};
    
    char (*p1)[2] = &a;    // 强制转换
    char (*p2)[2] = a;     // 强制转换
    
    printf("p1 = %d, p1 + 1 = %d\n", p1, p1 + 1);
    printf("p2 = %d, p2 + 1 = %d\n", p2, p2 + 1);
    
    return 0;
}

/*
p1 = -645891563, p1 + 1 = -645891561
p2 = -645891563, p2 + 1 = -645891561
*/

(2)

#include <stdio.h>

int main()
{
    char a[3] = {'a', 'b', 'c'};
    
    char (*p1)[4] = &a;    // 强制转换
    char (*p2)[4] = a;     // 强制转换
    
    printf("p1 = %d, p1 + 1 = %d\n", p1, p1 + 1);
    printf("p2 = %d, p2 + 1 = %d\n", p2, p2 + 1);
    
    return 0;
}

/*
p1 = -1015742635, p1 + 1 = -1015742631
p2 = -1015742635, p2 + 1 = -1015742631
*/

通过这一节的内容,大家是不是对数组名这个东东有了更深一步的认识呢?!哈哈,世上无难事,只怕有心人!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值