C学习:数组名赋值给指针后地址算术运算辨析

实例分析


取数组首位元素的地址和取数组名、取数组名的地址有啥区别呢?首先看看以下代码,看看你是否知道,printf输出的结果?

// 假设数组a[]起始地址为0x000000
float a[10] = {
   0.0f};
float *p1, *p2, *p3;

// 测试1
p1 = &a[0];
p2 = a;
p3 = &a;
printf("%d \n", p1);
printf("%d \n", p2);
printf("%d \n", p3);

// 测试2
p1 = &a[0] + 1;  
p2 = a + 1;  
p3 = &a + 1; 

printf("%d \n", p1);
printf("%d \n", p2);
printf("%d \n", p3);

请问以上两个测试代码,你的答案都对了吗?

// 测试1:输出结果全相同
0x000000
0x000000
0x000000

// 测试2:前两个相同,最后一个十进制值比前者多40
0x000000
0x000000
0x000028

如果你对里面的内部运算过程还不太清楚,不妨接着往下看。这个问题如果没搞清楚,在指针赋值运算和内存取用时,极易出现bug,而且一般不会报错,但是不小心弄混后,隐藏bug较难排查。

测试代码


【数组地址赋值指针后,地址算术运算辨析】下面结合完整测试代码,利用注释循序渐近解释其过程。

    float a[10] = {
   0.0f};
    float *p1, *p2, *p3;
    
    p1 = &a[0];  // 首位元素地址
    p2 = a;      // 数组名即指向的首位元素,相当于&a[0]
    p3 = &a;     // 对数组名取地址,虽然地址值一样,但是类型是整个数组
    // 以下输出结果相同
    printf("%d \n", p1);
    printf("%d \n", p2);
    printf("%d \n", p3);
    
    p1++;   // 往后偏移一个float元素, 等价于 p1 += 1;
    p2++;   // 计算机一个地址对应存储1个byte内存大小
    p3++;   // 地址往后偏移4个byte,也就是p3的实际值加4
    // 以下输出结果相同
    printf("%d \n", p1);
    printf("%d \n", p2);
    printf("%d \n", p3);
    // 类似的,以下输出结果也相同
    printf("%d \n", &p1[1]);  // 地址往后偏移4个byte,一个float空间大小
    printf("%d \n", &p2[1]);
    printf("%d \n", &p3[1]);  // 本质都是移动的p对应的float类型大小
    
   
    p1 = &a[0] + 1;  // 偏移4个byte
    p2 = a + 1;      // 偏移4个byte,效果类似于取&a[0]
    p3 = &a + 1;     // 偏移一个数组大小,40个byte;&a代表的是一个数组类型的指针,而非元素
                     // 而如果赋给p3后,p3再加1的话,则还是按p3的float类型
                     // 往后偏移4个byte(1个float大小的空间)
    // 以下输出结果不同
    printf("%d \n", p1);
    printf("%d \n", p2);
    printf("%d \n", p3);