指针问题专用贴

指针作为形参肯定有很多都不清楚其中具体的原理,我也是最近摸清了些门道:


下面就用一些例子来给大家说明:

[cpp]  view plain  copy
  1. void myMalloc(char *s) //我想在函数中分配内存,再返回   
  2. {   
  3.     s=(char *) malloc(100);   
  4. }   
  5. void main()   
  6. {   
  7.     char *p=NULL;   
  8.     myMalloc(p); //这里的p实际还是NULL,没有能分配到100字节空间,为什么?   
  9.     if(p)   
  10.        free(p);  
  11. }   

 
基于上面的问题: 因为指针作为形参传递过去的是它的拷贝,只是说他们指向同一块内存地址。 

    上面其实是给他的拷贝体进行了内存地址分配

[cpp]  view plain  copy
  1. void myMalloc(char **s)   
  2. {   
  3.     *s=(char *) malloc(100);   
  4. }   
  5. void main()   
  6. {   
  7.     char *p=NULL;   
  8.     myMalloc(&p); //这里的p可以得到正确的值了   
  9.     if(p) free(p);   
  10. }   

这里为什么能得到正确的值了呢? 

因为这里形参分配的内存其实是给s本身在分配了 ,上面分配的时候用的是(*s)

[cpp]  view plain  copy
  1. #include<stdio.h>   
  2. void fun(int *p)   
  3. {   
  4.     int b=100;   
  5.     p=&b;   
  6. }   
  7. main()   
  8. {   
  9.     int a=10;   
  10.     int *q;   
  11.     q=&a;   
  12.     printf("%d/n",*q);   
  13.     fun(q);   
  14.     printf("%d/n",*q);   
  15.     return 0;   
  16. }   
这里打印的结果为: 10  10

为什么呢? 因为main函数中q指针作为形参传递给了fun函数,fun函数中p为q指针的拷贝体,f

fun函数中的p在函数体中重新指向了整数b的地址。所以q指针本身的值是没有发生变化的


[cpp]  view plain  copy
  1. #include<stdio.h>   
  2. void fun(int *p)   
  3. {   
  4.     *p=100;   
  5. }   
  6. main()   
  7. {   
  8.     int a=10;   
  9.     int *q;   
  10.     q=&a;   
  11.     printf("%d/n",*q);   
  12.     fun(q);   
  13.     printf("%d/n",*q);   
  14.     return 0;   
  15. }   

这里打印的结果是: 10  100

这里可以正确打印我们想要的结果了,因为main函数中的q指针和形参中的p指针都指向了整数a的地址。

fun函数中其实是修改整数a地址中的值,所以在后文中打印指针q中的内容时,也能打印出100了



指针作为函数参数


鉴于很多人对“函数参数传递”这一块不理解,比如问int &a与int*&a的区别等等,我根据自己的理解,以容易理解的方式总结了一下,总共有八种(可能还有)。这只是我能想到了的。请大家继续多多指教,拍砖也可以!
另外声明:为了便于解释,我把讲解都以注释的形式写在程序中了。不习惯的人就迁就看吧!

C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

#include <iostream>
using  namespace  std;
/*为了便于区分讲解,函数中的临时参数标识符不用void fun1(int x,int y)代码中的x,y而用a与b替换
*另外,程序解释不用太多的语法,用通俗的话语依旧可以很有趣。“千言万语不如一个好例子”。下面
*我就遵循这个原则去解释我对问题的理解。
*/
 
/*fun1中的参数ab是在函数fun1调用的时候才分配内存单元,也就是临时变量,在函数调用结束后
*自动销毁。当调用fun1(x,y)时,实际相当于fun1(int a=x,int b=y)或者fun1(int a=*x0,int b=*y0);
*因此ab有自己的内存,通过调用ab也有了自己的值,那么在函数中无论对ab的什么操作,都只影响到ab,
*而与xy无关。
*用一个比喻更好解释:假设我国要引进俄国某个机型的战斗机生产线,那么通过政府(main())批准审核投资
*这个生产线就建造成了(fun1());那么这个生产线与俄国内的同样的生产线之间是相互独立的
*(ab与xy的区别),唯一的联系是:通过政府(main())出钱购买他们的技术或者设配(调用,同时a=x,b=y)
*经过这样之后,我们无论在这个生产线上造什么牛逼的歼十,十一。都与俄国本部的生产线无关了,他们关不
*着也问不着!
*/

void  fun1( int  a, int  b)
{ //这个函数交换数值是:治标不治本
     int  tem;
     tem=a;
     a=b;
     b=tem;
     cout<< "fun1(int a,int b):a=" <<a<< ",b=" <<b<<endl;
}
 
/*fun2数值的交换效果依旧是治标不治本。而且要注意它与fun3的区别,虽然它们形参格式相同
*临时开辟三个临时指针,a、b、tem。同样三者都是局部变量,与函数“老大”“同年同月同日同时生”
*而且“同年同月同日同时死”(很像三国的桃园三结义,都是忠诚的卫士)。fun2中的指针ab存储的
*是x,y的地址,等价于fun2(int *a=&x,int *b=&y)或者fun2(int *a=x0,int *b=y0);fun2中的代码
*实现的是把指针ab存的地址值交换一下,实质是交换了ab指针的指向,因此ab存储的地址中存储
*值还是没有改变。而且也不影响外界指针x0y0的指向,在fun7中会看到与不同
*可以通过fun2中注释掉的语句验证一下
*/
void  fun2( int  *a, int  *b)
{
     //cout<<"a存的地址值"<<a<<"\nb存的地址值"<<b<<endl;
     //cout<<"a指向的值"<<*a<<"\nb指向的值"<<*b<<endl;
     int  *tem;
     tem=a;
     a=b;
     b=tem;
     cout<< "fun2(int *a,int *b):*a=" <<*a<< ",*b=" <<*b<<endl;
     //cout<<"a存的地址值"<<a<<"\nb存的地址值"<<b<<endl;
     //cout<<"a指向的值"<<*a<<"\nb指向的值"<<*b<<endl;
}
 
 
 
/*fun3交换数值的效果才算治本了!
*局部变量问题不再解释。通过临时指针ab中存储的地址,找到真正要交换的xy值,然后把他们交换了!
*公安局喜欢这样干活,他们总是不能直接找到犯罪主谋,但是他们会找到一些知道主谋线索的其他非主谋
*嫌疑犯,那么通过抓住他们然后用刑折磨,这要比直接找主谋要容易些,当然这样做有些曲折(绕弯)!但也很有效
*同理,假设你在家里建一个小金库(这里的xy),那么你要设置好自己的密码(xy的地址),然后死死地
*记在脑海(指针)中。每当想打开看看让自己踏实些,你就要从脑海中找到这个密码然后打开它。然而技术
*在飞跃,假设已有《盗梦空间》中的技术,我想改变你的意念(fun3)或者从梦中偷你的思想或者秘密
*接下来的情节会更精彩...
*/
void  fun3( int  *a, int  *b)
{
     int  tem;
     tem=*a;
     *a=*b;
     *b=tem;
     cout<< "fun3(int *a,int *b):*a=" <<*a<< ",*b=" <<*b<<endl;
}
 
 
/*fun4表述的是“引用”
*引用的通俗解释:相当于一个人有多个名字或者外号一样。无论你叫他哪个名字,他都应。
*因此引用很像:一个变量有多个标识符(这样说稍微有些不当)。如果a是变量b的引用,那么ab是同一对象
*通过调用fun4(x,y)或者fun4(*x0,*y0).那么临时变量ab分别是xy与*x0与*y0的引用了。对ab操作就相当于在
*main中直接操作xy
*/
void  fun4( int  &a, int  &b)
{
     int  tem;
     tem=a;
     a=b;
     b=tem;
     cout<< "fun4(int &a,int &b):a=" <<a<< ",b=" <<b<<endl;
}
 
 
/*fun5参数采用指向指针的指针,这比fun2,fun3还要绕弯子,但这里依旧要注意fun5与fun7的区别
*fun5依旧治标不治本,fun6才能治本,他们实质和fun2与fun3类似,只不过比它们要多一层指针
*既然多一层指针那么在调用的时候传递的实参就必须是指针的地址,而不能是别的。等价于
*fun5(int **a=&x0,int **b=&y0)
*换用上面的比喻:相当于在《盗梦空间》里进入第二层梦里偷意念了。c++里没有拓展到第三,四层梦里
*去偷东西!也就是没有***a或者*****a。因为两层处理的很好了!
*/
void  fun5( int  **a, int  **b)
{
     int  **tem;
     tem=a;
     a=b;
     b=tem;
     cout<< "fun5(int **a,int **b):**a=" <<**a<< ",**b=" <<**b<<endl;
}
 
 
/*fun6和fun3有相同的实质,只是多一层指针
*/
void  fun6( int  **a, int  **b)
{
     int  tem;
     tem=**a;
     **a=**b;
     **b=tem;
     cout<< "fun6(int **a,int **b):**a=" <<**a<< ",**b=" <<**b<<endl;
}
 
/*fun7实现与fun2不同效果,上面我已经提到了。
*fun2结果是函数内部的交换不会影响到外面指针的指向,但是这里不同了,fun7函数实现了指针x0y0
*指向的交换。但却没有影响到xy的值的交换。就因为多了一个“引用(&)”。而fun2中是开辟的临时指针变量
*明白引用就能明白这里的效果。
*/
void  fun7( int  *&a, int  *&b)
{
     int  *tem;
     tem=a;
     a=b;
     b=tem;
     cout<< "fun7(int *&a,int *&b):*a=" <<*a<< ",*b=" <<*b<<endl;
}
 
/*这里达到的效果同fun3,二者一定程度上可以互相替换,只不过调用时有些区别!
*/
void  fun8( int  *&a, int  *&b)
{
     int  tem;
     tem=*a;
     *a=*b;
     *b=tem;
     cout<< "fun8(int *&a,int *&b):*a=" <<*a<< ",*b=" <<*b<<endl;
}
int  main()
{
     int  x=13,*x0=&x,&x1=x;
     int  y=250,*y0=&y,&y1=y;
 
     cout<< "\n调用前main():x=" <<x<< ",y=" <<y<<endl;
     fun1(x,y);
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
     //fun1(&x,&y);//错误
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun1(*x0,*y0); //这与上面的fun1调用没有区别
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
 
 
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun2(x0,y0); //把指针的地址传递过去,
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
 
     cout<< "\n调用前main():x=" <<x<< ",y=" <<y<<endl;
     fun2(&x,&y); //把指针的地址的副本传递过去(按值传递),依旧不会影响原来的指针x0y0的指向
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
 
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun3(x0,y0);
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
     cout<< "\n调用前main():x=" <<x<< ",y=" <<y<<endl;
     fun3(&x,&y);
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
 
 
 
     cout<< "\n调用前main():x=" <<x<< ",y=" <<y<<endl;
     fun4(x,y);
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun4(*x0,*y0);
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
 
 
     //fun5(&x,&y);//错误
 
     cout<< "\n调用前main():x=" <<x<< ",y=" <<y<<endl;
     fun5(&x0,&y0);
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun6(&x0,&y0);
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
 
//这里之所以把fun8调用放在fun7之前,是因为如果fun7在fun8前,那么fun7调用后x0指向y,y0指向x,
//再调用fun8得到的结果不便于理解。
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun8(x0,y0);
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
 
 
     cout<< "\n调用前main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     fun7(x0,y0);
     cout<< "调用后main():*x0=" <<*x0<< ",*y0=" <<*y0<<endl;
     cout<< "调用后main():x=" <<x<< ",y=" <<y<<endl;
 
     //fun7(&x,&y);//错误
 
    system ( "pause" );
    return  0;
}


<think> 嗯,用户这次聚焦在51单片机中间接寻址寄存器Ri的具体作用上,特别要求解释"Ri表示作为间接寻址的地址指针R0~R1"这句话的深层含义。从对话历史看,用户已经掌握了Ri的基本概念,现在需要更深入的技术细节。 用户可能正在学习51单片机的寻址模式,尤其是间接寻址这种高效的内存访问方式。ta引用的两段资料很关键:引用[1]说明R0/R1的8位特性决定了256字节寻址范围,引用[2]则对比了直接寻址的区别。这提示我需要重点阐释"地址指针"这个抽象概念如何通过具体寄存器实现。 考虑到用户上次对寄存器组切换也很关注,这次解释应该保持技术连贯性。需要强调三个核心点:一是R0/R1作为指针时的特殊地位(其他Ri不能用于间接寻址),二是256字节寻址范围与寄存器位宽的数学关系(2^8=256),三是这种设计在实时控制中的效率优势。 特别要注意用户出的系统指令要求:所有公式必须用LaTeX规范呈现。比如计算寻址范围时应该写成$2^8=256$,并在解释地址计算过程时使用$@R1 + 偏移量$这样的标准格式。中断现场保护的例子正好可以展示如何用Ri实现快速寄存器组切换。 最后生成的问题要延续用户的学习路径:从指针操作(问题1)到实际应用(问题2),再到扩展对比(问题3)。尤其问题3关于DPTR与Ri的对比,既能复习直接寻址知识,又能引出更强大的16位寻址方式,为后续学习埋下伏笔。 </think>在51单片机中,“**Ri 表示作为间接寻址的地址指针R0~R1**” 这句话的核心含义是:**寄存器 R0 和 R1 被设计为专用的间接寻址指针**,用于高效访问内存单元。以下是详细解释: --- ### 一、间接寻址的本质 间接寻址是指**操作数的地址存储在另一个寄存器中**,而非直接在指令中给出。在51单片机中: - **专用指针寄存器**:只有 **R0 和 R1** 能作为间接寻址的地址指针(其他R2~R7无此功能)。 - **操作语法**:指令格式为 `@Ri`(`i=0或1`),表示“**Ri寄存器中存储的值是一个地址**,操作数位于该地址指向的内存单元”。 **示例指令**: - `MOV A, @R0`:将R0指向的内存单元内容加载到累加器A。 - `MOV @R1, #55H`:将立即数55H存入R1指向的内存单元。 --- ### 二、寻址范围:256字节的限制 R0/R1作为指针时,寻址范围为 **256字节**(即 $2^8 = 256$),原因如下: 1. **寄存器位宽限制**: R0和R1均为8位寄存器,能表示的地址范围为 $0 \sim 255$($00\text{H} \sim \text{FFH}$)。 2. **物理内存划分**: - **内部RAM低128字节**(00H–7FH):可直接通过`@Ri`访问。 - **特殊功能寄存器(SFR)**(80H–FFH):**不可**通过`@Ri`访问,需用直接寻址[^2]。 - **外部RAM**:需配合`MOVX`指令(如`MOVX A, @R1`)。 > 📌 **关键点**: > 因R0/R1是8位寄存器,其指针能力被限制在256字节内。若需更大范围寻址(如64KB),需使用16位指针寄存器**DPTR**(如`MOVX A, @DPTR`)。 --- ### 三、与直接寻址的区别 | **特性** | **间接寻址(@Ri)** | **直接寻址** | |----------------|-----------------------------------|----------------------------------| | **指令格式** | `MOV A, @R0` | `MOV A, 30H` | | **操作数来源** | R0/R1中的**地址值** | 指令中的**直接地址** | | **适用范围** | 内部RAM低128B、外部RAM | 内部RAM低128B、SFR[^2] | | **效率** | 更高(适合循环访问连续内存) | 较低(需硬编码地址) | **示例对比**: ```assembly ; 直接寻址:将30H单元内容送A MOV A, 30H ; 指令中包含地址30H ; 间接寻址:将R1指向的单元内容送A MOV A, @R1 ; R1中存储了目标地址(如30H) ``` --- ### 四、Ri作为指针的实际应用 1. **遍历数组/缓冲区**: ```assembly MOV R0, #40H ; 数组首地址存入R0 LOOP: MOV A, @R0 ; 读取当前元素 INC R0 ; 指针指向下一元素 CJNE R0, #50H, LOOP ; 循环直到地址50H ``` 2. **中断现场保护**(快速切换寄存器组): ```assembly ; 中断服务程序中切换至Bank 1 SETB PSW.3 ; PSW.3=1, PSW.4=0 → 选择Bank 1 MOV R0, #data ; 使用Bank 1的R0暂存数据 ``` --- ### 五、为何限定R0/R1? 51单片机设计时权衡了硬件成本与效率: 1. **指令集精简**:仅开放R0/R1支持间接寻址,减少指令解码复杂度。 2. **实时性需求**:R0/R1作为高频使用的指针,访问速度比直接寻址更快。 3. **资源优化**:避免所有通用寄存器都支持间接寻址,节省硅片面积。 --- ### 总结 - **Ri 专指 R0/R1**:它们是唯一支持间接寻址的通用寄存器。 - **指针功能**:`@Ri`表示“**取Ri中的值作为地址**,操作该地址的数据”。 - **寻址范围**:8位地址空间(256字节),覆盖内部RAM低128B和外部RAM。 - **核心价值**:提升数据存取效率,简化循环、数组等操作。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值