吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 692|回复: 7
收起左侧

[讨论] C语言-数组指针

[复制链接]
ATrueMan 发表于 2023-5-6 13:00

1.1 数组指针

指针数组和数组指针常常让人感到混淆。

从词性的角度理解。指针数组是指针修饰数组,强调的是数组。数组指针是数组修饰指针,强调的是指针。

从C语法定义来看:二者定义的语法不同。如果int (*p)[5] 不加括号,就变成了指针数组int *p[5]。

int main() {
    int array[5] = { 1, 2, 3, 4, 5 };
    int* arr[5];  // 指针数组。数组arr中存储的都是int型的指针
    int (*p)[5];  // 数组指针p。指针p指向一个长度为5个int的数组首地址
    p = (int(*)[5])arr;  // 数组指针p指向arr的首地址

    printf("%d %d\n", p, *p);

    return 0;
}
输出:
    11533108 11533108

从汇编代码看:注意默认是cdel调用约定,printf的参数是从右往左压栈。可以看到p和*p的值都是来自[ebp-44h]。因此对于cup而言,二者是没有区别的。

我认为p和*p是编译器用于某种区分的。

printf("%d %d\n", p, *p);
006450D8 8B 45 BC             mov         eax,dword ptr [ebp-44h]  
006450DB 50                   push        eax  
006450DC 8B 4D BC             mov         ecx,dword ptr [ebp-44h]  
006450DF 51                   push        ecx  
006450E0 68 DC 7B 64 00       push        647BDCh  
006450E5 E8 C7 C2 FF FF       call        006413B1  
006450EA 83 C4 0C             add         esp,0Ch  

验证我的假设

int main() {

    int arr[5] = { 1, 2, 3, 4, 5 };
    int(*p)[2] = (int(*) [2])arr;

    printf("%d %d\n", p, *p);

    printf("%d %d %d\n", p, p+1, *(*(p+1)));

    printf("%d %d %d\n", *p, (*p)+1, *(((*p)+1)+0));

    return 0;

}
输出:
5241652 5241652
5241652 5241660 3
5241652 5241656 2
printf("%d %d %d\n", p, p + 1, *(*(p + 1)));
00D450ED B8 04 00 00 00       mov         eax,4  
00D450F2 6B C8 00             imul        ecx,eax,0  
00D450F5 8B 55 D8             mov         edx,dword ptr [ebp-28h]  
00D450F8 8B 44 0A 08          mov         eax,dword ptr [edx+ecx+8]  ; +8
00D450FC 50                   push        eax  
00D450FD 8B 4D D8             mov         ecx,dword ptr [ebp-28h]  
00D45100 83 C1 08             add         ecx,8  ;  p + 1 --> +8
00D45103 51                   push        ecx  
00D45104 8B 55 D8             mov         edx,dword ptr [ebp-28h]  
00D45107 52                   push        edx  
00D45108 68 E4 7B D4 00       push        0D47BE4h  
00D4510D E8 9F C2 FF FF       call        00D413B1  
00D45112 83 C4 10             add         esp,10h  

    printf("%d %d %d\n", *p, (*p)+1, *(((*p)+1)+0));
00D45115 8B 45 D8             mov         eax,dword ptr [ebp-28h]  
00D45118 8B 48 04             mov         ecx,dword ptr [eax+4]  ; +4
00D4511B 51                   push        ecx  
00D4511C 8B 55 D8             mov         edx,dword ptr [ebp-28h]  
00D4511F 83 C2 04             add         edx,4  ; (*p)+1 --> +4
00D45122 52                   push        edx  
00D45123 8B 45 D8             mov         eax,dword ptr [ebp-28h]  
00D45126 50                   push        eax  
00D45127 68 E4 7B D4 00       push        0D47BE4h  
00D4512C E8 80 C2 FF FF       call        00D413B1  
00D45131 83 C4 10             add         esp,10h  

实验结果证明:

  1. p和*p存储的值相等

  2. p+1和*p+1的值不相等。

    p+1:5241652 - 5241660 = 8 --> 2个int

    *p+1:5241652 - 5241656 = 4 --> 1个int

    证明p和*p的数据宽度不同。p是2个int宽度,*p是1个int宽度。表明:p指向的是arr首地址,是一个具有2个int宽度的数组的首地址。*p指向arr数组第一个元素的地址,其宽度是1个int宽度。

  3. *(*(p+1))。p+1使得p向高地址移动1*2个int宽度。*(p+1)将移动宽度变为1个int宽度。*(*(p+1))取出指向地址的值:3。*(*(p+1)) == *(*(p+1)+0) == *(p+1)[0]

    *(((*p)+1)+0)。*p将移动宽度变为1个int宽度。(*p)+1使得p向高地址移动1*1个int宽度。((*p)+1)+0使得p向高地址移动0*1个int宽度。*(((*p)+1)+0)取出指向地址的值:2。*(((*p)+1)+0) == *((*p)+1+0) == ((*p)+1)[0]

结论:

数组指针中,p和*p存储的值相等,是编译器用于区分不同数据宽度的一种标记方式。即p指向的是数组首地址,加减运算是按照数组宽度计算。*p指向数组的第一个元素的地址,加减运算是按照数组元素的宽度计算。

鄙人陋见,批评指正!

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
AItechnology + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

borohai 发表于 2023-5-6 17:18
刚学完这里,小白还啥都不懂,刚好看看跟着学习一下
 楼主| ATrueMan 发表于 2023-5-8 10:39
borohai 发表于 2023-5-6 17:18
刚学完这里,小白还啥都不懂,刚好看看跟着学习一下

感谢支持!你觉得这篇文章写的通俗吗,有没有什么地方晦涩难懂。
borohai 发表于 2023-5-8 10:43
ATrueMan 发表于 2023-5-8 10:39
感谢支持!你觉得这篇文章写的通俗吗,有没有什么地方晦涩难懂。

主要的是我刚学,很多不懂,汇编都看不懂,最多认识了指针数组与指针数组,还不会用,但是你的那个p与*p那个太高级了,我看不明白
熊猫拍板砖 发表于 2023-5-9 15:19
本帖最后由 熊猫拍板砖 于 2023-5-9 16:11 编辑

int (*p)[5],这个一般用在二维数组上,一维数组直接int *p就行了
你上面的操作有点迷之操作,既然操作一维数组,为啥非要把一维数组强制转换成一个二维数组指针去操作,有点化简为繁了。

#include "stdio.h"

int main(void)
{
                int arr[5]={1,2,3,4,5};
                int (*p)[5];
                int *p1;
                // 这里把 arr 赋值给 p
                p=arr;   // 这里虽然不会报错,但是会告警 Incompatible pointer types assigning to 'int (*)[5]' from 'int[5]'; take the address with &
                // 意思是 将'int[5]'分配给'int (*) [5]'时,指针类型不兼容;请使用&获取地址

                // 这里把arr 赋值给 指针p1,就没有什么问题
                p1=arr;

                // 分别对 p1 和 p 进行指针操作

                // 对p1进行指针操作 
                for (int i=0;i<5;i++)
                {
                                printf("p1 = %d, &p1 = %p\n",*(p1),p1);
                                p1++;
                }

    // 对 p 进行指针操作
    for (int i=0;i<5;i++)
    {
        printf("p = %d,&p = %p\n",*((*p)+i),(*p)+i);

    }
                return 0;
}

结果:

p1 = 1, &p1 = 0x7ff7bdb1b830
p1 = 2, &p1 = 0x7ff7bdb1b834
p1 = 3, &p1 = 0x7ff7bdb1b838
p1 = 4, &p1 = 0x7ff7bdb1b83c
p1 = 5, &p1 = 0x7ff7bdb1b840
p = 1,&p = 0x7ff7bdb1b830
p = 2,&p = 0x7ff7bdb1b834
p = 3,&p = 0x7ff7bdb1b838
p = 4,&p = 0x7ff7bdb1b83c
p = 5,&p = 0x7ff7bdb1b840


通过上面的例子可以看到,如果你要是对一维数组进行操作,最好不要使用二维数组的指针,那样是非常麻烦的

因为多维数组中,比如 int arr[2][4]; 这里最外维的指针+1,相当于内存地址+16字节长度,而最内层维度,指针+1也就是增加4字节长度

这里推荐两本书,第一本,有没有基础都可以看的 《C Primer Plus》,第二本是《深入理解C指针》,这本是有C基础的来看的
 楼主| ATrueMan 发表于 2023-5-11 22:27
熊猫拍板砖 发表于 2023-5-9 15:19
int (*p)[5],这个一般用在二维数组上,一维数组直接int *p就行了
你上面的操作有点迷之操作,既然操作一 ...

感谢你的观点!你的解释非常正确!从二维数组的角度理解 int (*p)[5]; p就是指向的二维数组中第一个维度的地址。
这篇文章主要是想解释下一般指针和数组指针对于*的处理。*p的值为什么是一个地址却不是一个值,而 *p1的值为什么是一个值。

此外,arr与&arr它们的值相同,但是表示的数据宽度却不一样。&arr:数组指针。

我认为你和我的看法没有本质的区别,只是看待的出发点不同。从正向的角度理解,有许多专有名词能够帮助我们快速理解。从逆向的角度理解,都是操作一段内存,只是数据宽度不同。
希望我们的理解能给大家带来帮助!

int main() {

    int arr[5] = { 1, 2, 3, 4, 5 };

    printf("arr:%x \t&arr:%x \tarr+1:%x \t&arr+1:%x\n", arr, &arr, arr+1, &arr+1);

    return 0;
}
输出:
    arr:cff9c8      &arr:cff9c8     arr+1:cff9cc    &arr+1:cff9dc
lijialong1313 发表于 2023-5-12 11:11
你这结论描述的很奇怪,容易让人更乱。

首先,“编译器用于区分不同数据宽度的一种标记方式”,这东西有个更加适合的名词叫“类型”。这也是内存存储的本质,即你后面结论的“宽度”实际上就是每个类型所占用的宽度。

其次,不加括号实际上不是因为“定义语法”不同,实际上这个是因为[]运算符早于*运算符运算,因此不加括号会先执行p[5],再执行*p。

最后,这俩东西实际上没什么混淆的,也和强调没有什么关系。“指针数组”的描述就和整型数组,浮点型数组一致,前面代表集合的类型,后面代表它是一个集合。而“数组指针”也和整型指针,浮点型指针一致,前面代表这个指针的类型,后面代表它是一个指针。这和强调什么并没有关系,只是一种简单的子类型定义而已。
 楼主| ATrueMan 发表于 2023-5-15 21:31
lijialong1313 发表于 2023-5-12 11:11
你这结论描述的很奇怪,容易让人更乱。

首先,“编译器用于区分不同数据宽度的一种标记方式”,这东西有 ...

感谢你的讲解
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 22:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表