突然对指针的感悟,不知道正确与否。

program2050 2012-02-23 12:49:55

感悟1:
指针中保存的内容是地址

感悟2:
int *a = NULL, b = 10;
a = &b;
a 等价于 *(&a),等价于&(*a)
*a 同样等价于 *(*(&a))
...全文
2157 91 打赏 收藏 转发到动态 举报
写回复
用AI写文章
91 条回复
切换为时间正序
请发表友善的回复…
发表回复
潇湘夜雨 2012-02-29
  • 打赏
  • 举报
回复
咬概念,么得啥子意思
Ever_lover 2012-02-29
  • 打赏
  • 举报
回复
*是指向什么什么的意思,&是取呢个地址的内容,有点不同
complayer 2012-02-29
  • 打赏
  • 举报
回复
lvalue必须是一块内存区域,等同于变量的表达式,具体形式可能多种多样。
比如: a, b[3], c.value,*d->next等等。
根据这个表达式必须能够确定某块内存区域。
这玩意好像还真得用汇编才能说清楚,它是[100]和100的区别,[100]告诉CPU去100这个地址去取操作数,100则是把100当作操作数。如果去看具体的指令,则是寻址方式的差异,间接寻址和立即寻址。
比如:
MOV [100], 33 ; 把33存入地址为100的位置
ADD [100], 100 ; 从地址100的位置取第一个操作数33,加上立即数100,结果133存入[100]。
; 最后结果是内存地址为100的位置值变为133。
如果我们把[100]命名为int a,那么就等同于下面的语句:
a = 33;
a += 100;
complayer 2012-02-29
  • 打赏
  • 举报
回复
lvalue必须是一块内存区域,等同于变量的表达式,具体形式可能多种多样。
比如: a, b[3], c.value,*d->next等等。
根据这个表达式必须能够确定某块内存区域。
这玩意好像还真得用汇编才能说清楚,它是[100]和100的区别,[100]告诉CPU去100这个地址去取操作数,100则是把100当作操作数。如果去看具体的指令,则是寻址方式的差异,间接寻址和立即寻址。
比如:
MOV [100], 33 ; 把33存入地址为100的位置
ADD [100], 100 ; 从地址100的位置取第一个操作数33,加上立即数100,结果133存入[100]。
; 最后结果是内存地址为100的位置值变为133。
如果我们把[100]命名为int a,那么就等同于下面的语句:
a = 33;
a += 100;
complayer 2012-02-29
  • 打赏
  • 举报
回复
我发现表述上还是有问题。
变量应该是一块内存区域的别名(连续或不连续)。
我们通常把一块内存区域的首地址作为它的的地址,也就是&操作符取到的值,指定时刻它是一个常数,一个标量值。
秦剑 2012-02-29
  • 打赏
  • 举报
回复
指针是一个内存单元,是一个变量
complayer 2012-02-28
  • 打赏
  • 举报
回复
通俗地讲左值(lvalue)是能够放在赋值操作符左侧的表达式,右值(rvalue)是只能放在赋值操作符右侧的表达式。赋值是往特定的内存地址写入内容,所以lvalue必须有确定的内存地址,否则没法写入,或者写入后很快被覆盖,比如只保存在寄存器上的值,临时变量(函数的返回值)等。

1+2这个表达式的结果就是一个rvalue,有值但没有确定的地址。
int a; 变量a就有确定的地址,可以读取内容也可以写入内容,比如a=3。a是一个lvalue。
反之a+3就是一个rvalue,只是一个临时的值,不立即保存的话就没法访问了。

lvalue的值该语句结束后还可以访问。
rvalue的值如果在本语句不保存的话,就不能访问了。

int *p, a[10];

p = a;

p; // lvalue

*p; // lvalue

p+1; // rvalue,是指针值

*(p+1); // lvalue

*p+1; // rvalue

a; // lvalue

&a; // rvalue,是指针值

a+1; // lvalue

*a; // rvalue

(int *)100; // rvalue,是指针值

容易混淆的是指针和指针值,正如变量是内存地址的别名一样,指针(全称指针变量)也是内存地址的别名,而指针值则是该内存地址中保存的值。上面的例子中p是一个指针变量,p的值是a的地址,32位系统中是一个32位的无符号整数,可以转换为整数打印出来。int a = 3; a是变量,某个内存地址的别名,a的值是3。p=NULL; p是指针,某个内存地址的别名,p的值是NULL(0)。

需要好好体会,呵呵。理解lvalue和rvalue如果有一定的汇编基础会容易点。上面很多的回帖说的都是关于指针这方面的特性。单个指针很好确定,如果是比较复杂的指针表达式到底是lvalue还是rvalue容易犯晕。

我发现指针的复杂性主要是*操作符引入的,它能将一个rvalue转变为lvalue。

*((int *)1000) = 999;

等价于:

int *p = (int *)1000;
*p = 999;

(int *)1000 = 999; // 是错误的。
怎么理解呢?1000是一个整型常数(rvalue),(int *)将其强制类型转换后还是常数(rvalue),只是类型发生了变化变成了整型指针值,原来是int,现在变成了int *。

&操作符返回的是一个指针值(rvalue),而不是指针,只是一个值而已。清楚这一点就容易了。

一个指针值(类似整数值)可以使用运算符有+, -, *,+或-的时候与指针值的数据类型有关,比如整型指针值+1,指针值增加的是sizeof(int),32位系统下为4。char指针值+1,指针增加的是sizeof(char)=1。

lvalue可以当作rvalue用,反之不行。因为我们可以把lvalue的值(rvalue)取出来参与运算。

muyi66 2012-02-28
  • 打赏
  • 举报
回复
那你这么理解:左值就是能放到赋值符('=')左边的值,左值表示存放数据的位置。
zyzhaojun 2012-02-28
  • 打赏
  • 举报
回复
[Quote=引用 76 楼 complayer 的回复:]

看了一下上面的系列回帖,写得好乱。主要是关于&操作符的返回值到底是lvalue还是rvalue,非常乱。

int a, b, *p1, **p2;

&a = &b; // 错误,虽然类型相同都是int *,但是因为&a是rvalue。

p1 = &a;

p2 = &p1;

&p1 = p2; //错误,虽然类型……
[/Quote]

你思考得很好!但最好用些贴近老百姓的通俗话来讲一下什么是左值,什么是右值?

莫讲一些“左值是能够被赋值的值,右值是能够给左值赋值的值”之类的循环论证的话,最好莫搬课本和术语,好让观众清楚明白。
低头思蚊香 2012-02-28
  • 打赏
  • 举报
回复
这贴也能上首页。。没什么意思啊
帅牛 2012-02-28
  • 打赏
  • 举报
回复
学习了,学习了。。。谢啦!~
complayer 2012-02-28
  • 打赏
  • 举报
回复
呵呵,没关系,俺不在乎分数,在乎结果。
通过回复,也让我梳理了一下自己的思路,加深了认识。
program2050 2012-02-28
  • 打赏
  • 举报
回复
[Quote=引用 71 楼 complayer 的回复:]

heronism说a等价于&(*a),这个不太理解。*a放的是b的值,a放的是b的地址,&(*a)一定要放b的地址吗,为什么不能是其他和b的值相等的变量的地址。

*a表达式是一个L-Value,是一个内存地址,所以可以用&运算符作用于它。至于为什么这样,因为*运算符就是这样定义的,呵呵。
&a返回的是一个R-Value,a == &(*a),但是……
[/Quote]
complayer 回答得好详细。可惜结贴给分得太早了。。。。
complayer 2012-02-28
  • 打赏
  • 举报
回复
rvalue通过*操作符能转换为lvalue,所以一定要关注该操作符。

&(*p)是rvalue;

*(&p)是lvalue;

就是这个道理。

单个变量是lvalue。

struct tagNode
{
int value;
struct tagNode *next;
} *root;

root = new tagNode;

root->next = new tagNode;

新创建的这两个元素都没有名字,只能通过指针来引用。

delete root->next;

delete root;


new操作符返回的也是一个指针值,而不是指针。

(*new tagNode).value =10;

这里出现内存泄漏了。
complayer 2012-02-28
  • 打赏
  • 举报
回复
表达式计算结果通常都是rvalue,是临时结果,必须保存起来才行。我们通常会把表达式计算结果通过赋值保存到特定的变量中。

指针通常用于实现动态数据结构,动态分配的元素没有名字,没有变量与之对应,无法直接引用,但是它们的地址可以通过某种方式计算出来。

此外处理数组时,虽然可以用下标引用,但是用指针表达式来引用更加方便,算法实现起来更简单。

再者是为了通用性和灵活性,比如函数指针,我可以定义一组相同函数原型的函数,根据需要指向特定的函数。比如说分情况处理,每种情况可以定义一个函数,这样主函数中只要设置函数指针指向特定的函数就可以了。真正的实现分布在各个具体的处理函数中,逻辑更为清晰,相互之间耦合较少,维护起来更容易。

还有就是回调函数,你写程序的时候要调用的函数还不存在。你可以通过检查函数指针是否为空,非空就调用,否则不调用。这样你的系统就具有可扩展性。
complayer 2012-02-27
  • 打赏
  • 举报
回复
p=NULL;
p=&p;
这个好像类型不一致吧。
int *p;
int **q;
q = &p;

世上本无事,庸人自扰之。

不要把指针想象得这么难,结果是自己给自己套上一副枷锁,故不自封,作茧自缚。

指针概念原本很简单,虽然*和&两个运算符比较特殊,真正的困难之处在于指针表达式的类型。就拿上面的例子来说,p的类型是int *,&p的类型是int **。
&&p有没有意义呢?呵呵!&p变成了一个R-Value,一个立即值,没有地址。

对于一个指针表达式弄清楚下面两点就行了:

1、这个指针表达式是L-Value还是R-Value。这很重要!
上面的例子来说p和*p是L-Value,p、*p,&p都可作为R-Value。

2、这个指针表达式的数据类型是什么。

*和&运算符作用于表达式会改变表达式的数据类型,这是指针表达式复杂的根源所在。

有人会拿汇编来说事,其实即便你精通了汇编也没用,因为汇编没有这么复杂的数据类型。BYTE, WORD, DWORD, QWORD就这些,还有有符号和无符号,这个要看具体的指令了。数据类型只在编译期间有效,到了汇编阶段这方面的信息已经没有了。当然你可以在程序里加上去,你就把这一串字节当作一个double处理,那就属于程序语义层面了,而不是语言层面。


i0073461 2012-02-27
  • 打赏
  • 举报
回复
太牛了。
[Quote=引用 14 楼 zhao4zhong1 的回复:]

VC调试(TC或BC用TD调试)时按Alt+8、Alt+6和Alt+5,打开汇编窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应内存和寄存器变化,这样过一遍不就啥都明白了吗。
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Linux或Unix下可以在用GDB调试时,看每句C……
[/Quote]
一线灵 2012-02-27
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 saleayas 的回复:]
都不对!
[/Quote]
莫要欲盖弥彰, 危言耸听
muyi66 2012-02-27
  • 打赏
  • 举报
回复
p=NULL;
p=&p;

指向自身的指针可以实现,但没有意义。
complayer 2012-02-27
  • 打赏
  • 举报
回复
看了一下上面的系列回帖,写得好乱。主要是关于&操作符的返回值到底是lvalue还是rvalue,非常乱。

int a, b, *p1, **p2;

&a = &b; // 错误,虽然类型相同都是int *,但是因为&a是rvalue。

p1 = &a;

p2 = &p1;

&p1 = p2; //错误,虽然类型相同都是int **,但是因为&p1是rvalue

我们不能给一个rvalue表达式赋值,只能给lvalue表达式赋值。


加载更多回复(70)

69,369

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧