关于取地址运算符&以及指针的问题

A_Zhao 2012-12-31 05:45:04
引用
至此,我想应该就是我没弄明白表达式这个概念。或者,是因为翻译的问题,产生的很多困惑。

这恐怕不是翻译的问题。如果在你所看的那本书里,出现了“取地址操作符即&,不能施加于表达式”这种说法的话,那么,这种说法是错误的。不过,考虑到这本书的特殊的背景,这种“错误”在某种程度上是可以被原谅的 —— 毕竟,如果那本书能将一切问题都讲细致的话,它就远远不能止于那个篇幅了。

首先,在排除其他意义的情况下,作为操作符的&,叫做“取地址操作符”(Address Operator)。然而,这种称呼,其实有相当多的弊端,比如,会令读者认为,由其得到的指针就是地址。而实际情况是,指针与地址是两个不同的范畴,无论如何都不能视同一致。所以,依照薛非大虾的思路,并且遵循„Zu den Sachen selbst!“的精神,我们不妨这样来描述操作符&:

它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。

理解这句话,有这样几个要点:

(1)&是操作符。既然是操作符,就有操作数,即操作符所施加的对象。

(2)或许,对于初学者,准确地理解“操作数”这个词,是有些障碍的。因为用汉字“数”来指称这个对象,难免让人望文生义地用初等数学里的“数”或代数表达式来理解它。所以,我们有必要以更加确切的境况下的事实,揭示:这里的“操作数”究竟指的是什么?

(3)&的操作数,非但可以是表达式,而且必须是表达式。不过,这种表达式,并不是C语言里的任意一个表达式,而是受制于某种规则的表达式。

(4)现在,我们引入了“表达式”这个范畴。这个概念,是问题的核心,也是许多问题胶着难解的所在。可以这麽说,一门编程语言在表达式这个范畴上的实际含义,体现了该门语言的特质。显然,能解释清楚这类事情,并非易事。我们还是以„Zu den Sachen selbst!“的思路继续发掘。

(5)在C语言中,为了平滑而自然地接受它在“表达式”这件事情上的准确含义,我们还得事先引入“对象”这个概念(跟“面向对象编程”里的“对象”不同)。粗略地说,在C语言的视角下,一切存在于内存空间上的区域,皆是对象。这里的内存空间,一般专指主存储器上可以承载数据的空间分布。详细讨论“主存储器”,恐怕要花上几本书的篇幅,现在我们只抓住几个重点:

(i) 主存储器上用来承载数据的空间分布,以“地址”这种机制来把握。—— 我们理解“地址”,不应该仅仅将其理解为一个数值范畴,而是应该用它的作用(为什么要用到它)来理解。从硬件视角看,连接在CPU与主存储器之间的“血脉”分别有地址总线、数据总线与控制总线等。从运作逻辑接口来看,主存储器在CPU这端,以用来放置前者上的地址的寄存器与用来放置前者上的数据的寄存器来呈现。

(ii)“地址”这种机制,在实际运用中,具有“连续”的特质(这种固有禀性,是关于指针的一些关键运算的基本前提)。而同是用来承载数据的寄存器(专指CPU的某个组成部分,不指其他的寄存器),则没有这种特质。从这个角度上,我们可以将主存储器与寄存器很清晰地分别开来。所以,用存储类别register限定的被声明的对象,不能成为&的操作数。但是,请特别注意:前述规定的理由,并不是在于register跟寄存器有一一对应的联系!而是在于:由register限定的变量,有可能被编译器分配到寄存器。register这个限定符的真确含义是:可以用它,来提示编译器在为变量分配存储空间的时候,[color=blue]编译器允许将变量分配到寄存器,从而弃绝可能发生的与该变量有关的“地址”与“指针”机制。[/color]

(6)回到“对象”(即存在于内存空间上的区域)这件事情。在C语言中,如何读取与刷写对象上的数据?C语言采取的策略,是左值表达式。不难理解,只有左值表达式可以出现在赋值等号(赋值操作符)的左边,有如:
int a, b, c, d, e, *ptr;
/* 上述部分变量须初始化,此略 */

a = b;
ptr = &a;
ptr[c] = 123;
* ptr = 456 + d;
*(ptr + e + 1) = 789;
在这上面代码中的后面五行里,
a、ptr、ptr[c]、*ptr、*(ptr+e+1)
都是左值表达式。

我们需要注意:上面所说的“只有左值表达式可以出现在赋值等号的左边”,并不意味着“左值表达式只可以出现在赋值等号的左边”。比如,在上列第一行代码中,b其实也是一个左值表达式,但它可以出现在赋值等号的右边。通过这样的比对,我们不难理解:在C语言中,采用左值表达式这种策略,来handle该表达式所引用(这专指一种更为一般的机制,与C++等语言专属术语“引用”不同)的对象,从而实现对对象上的数据进行读取或刷写的企图。比如:在赋值表达式a=b中,b首先是一个左值表达式,C语言用它来读取这个表达式即b所引用的内存区域上的数值,然后将这个数值,刷写到左值表达式a所引用的内存区域上。此外,需要提到的是:由于a这个“东东”,程序令它向程序自己开放了某种在任意(事先无法预知)时刻或时机被刷写进任意数据内容的权限(但须是合法的),所以,我们也以“变量”来描摹a。

与之形成对比的是,那些无法帮助C语言施行这种策略与企图的表达式,一般称作右值表达式,比如上列代码中的 456 + d,它是一个右值表达式,因为它无法帮助C语言handle任何它自己所引用的内存区域(这种对象根本不存在)。

(7)&的操作数,仅能是一个左值表达式。我们可以从“取地址操作符”这个称呼上,理解这件事情:&操作符帮助我们获取一个地址,即用以定位内存空间上的区域的一种特殊数据,那么,当&无法完成这个任务的时候,比如其操作数是一个不具备被获取地址的特性的某种东东(如register所限定而声明出来的变量、一个右值表达式),那么这样的操作数就是非法的。

(8)对于&操作符帮助我们所获得的数据(即返回值),许多人认为是一个地址,但在真确而实际的语义中,我们不如单纯而朴素地认为:该返回值,应该是一个指向其操作数的指针,而不是一个所谓的“地址”。为什么这麽说呢?

我们之前提到了,C语言采用了利用左值表达式来handle该表达式所引用的对象,比如:表达式a的存在,是为了引用某块“属于(对应于)”a的内存区域。C语言程序的宗旨,当然就是读取或刷写这块内存区域上的数据。所以,C语言程序实现这个宗旨的层次细节,大抵是这麽一种关系:

表达式a ----> 一块对应于a 的内存区域(一个内存对象) ----> 这块内存区域上的数据(值)

C语言采用“语句(断言)”这样的策略,围绕着左值表达式施行一种当时性的对内存区域上的数据的读取或刷写。这构成了C语言在命令式语言上的基本特征。

犹如一个*简单的*左值表达式可以引用一个内存对象,指针也可以引用一个内存对象。(而地址,只是用以在连续地铺张开来的内存空间中,定位内存对象的一种机制。所以,这也可以说明:指针跟地址,并不应该视同一回事。)不过,指针相对于一个*简单的*左值表达式来说,其引用机制是间接的。所以,为了能利用指针实行一个间接的引用(企图等效于直接的引用),我们就必须借助于间接访问操作符Indirection Operator),即让初学者恐惧而忧烦的那个星号。

这种含有间接访问操作符的表达式,称作间接访问表达式,有如:
*ptr
一般我们可以认为:间接访问操作符(即星号)作用在一个指针上面,从而使得整个间接访问表达式犹如一个*简单的*左值表达式那样,去handle某个内存对象。

这里,我们可以再一次将指针与地址两下撇开。在指针被用来实现引用机制的过程中,至少我们从寻常的代码及其运行的直观中,丝毫觉察不到任何的地址这种特殊数据的形态。换句话说,地址只是指针机制的某种幕后支撑,而这种支撑策略,并非是惟一的。比如,技术性地,我们也可以选择Hash的key-value对来实现指针的引用机制。

至此,我们不难理解,间接访问表达式,也是左值表达式。
因此,诸如
&*ptr
&*&i
这样的表达式,都是完全合法的。

(9)&和*操作符的使用,尤其是配对使用,极容易给初学者带来“C语言的指针是‘脱裤子放屁’”的印象。为了破除这种误解,必须将C语言的其他特质纳入我们的视野。在这些特质中,最具有代表性的,就是C语言的函数策略。

我们观察并思考如下代码:

void foo(int x) {
x++;
}
我们的期望:以foo这个函数,为被传至其中的参数本身,施行自增操作。

但是,不论我们调用多少次函数foo(a),我们在调用完成之后,变量a不会有任何变化。这是为什么?原因出于C语言固有的函数策略。具体地说:当我们以变量a为函数foo的参数的时候,函数foo将变量a的数据值刷写到该函数治下的变量x,也就是说,函数foo内部的变量x只是拥有了作为调用该函数的参数即变量a的数据值的私有副本。换句话说,函数foo内部的变量x,只是被初始化了一个值,至于这个值是从哪里来的,站在变量x的角度,它自己是永远无法知道的。这犹如:
x = a ;
作为左值的x,只是被赋值以另一个左值a所能handle的内存区域上的数据,除此之外,有关左值a自己的其他一切信息,都不会传达给x。所以,在执行了上述语句之后,分别对左值x和a的任何更改性操作,都不会影响到对方。

除此之外,当函数完成任务之后,该函数治下的一切私有变量,都将消亡 —— 这是导致所谓的“内存泄露”现象的根本原因,后面我们会讲到。

那么,我们为了利用一个函数,完成对被传入该函数变量的本身的某种更改性操作,我们应当这麽做:

void bar(int *x) {
(*x)++;
}
我们调用函数bar的写法,也应当有所改变,应当这样:
bar(&a);
此时,被传入函数bar的参数,是一个指向变量a的指针。函数bar治下的变量x(这是一个指针变量)所拥有的,依然仅仅是前者的的一个私有副本。

函数bar在其内部,通过把间接访问(星号)操作符作用在这个私有副本上,handle到了变量a所对应的那块内存区域,从而,间接地在那块区域上刷写了新的数据。当然,变量a自已对此是一无所知的,只不过,以后当其他地方又“召唤”a的时候,a若自有记性,会心说:“唉?这个值跟原来的不一样了嘛,一定是哪个臭小子,在背地里拿了一个指向我的指针去间接地修改了这个值……”

(10)最后,我们考虑一种情形:

void qux(void){
int *ptr = malloc(sizeof(int)*15);
}
函数qux内部调用了函数malloc,后者负责在当前内存中的空闲空间中分配出一块由其参数所确定大小的存储空间(那么,这新分配出来的存储空间将不再是空闲的,直到有函数free来释放之),并将指向这块被分配出来存储空间的指针,赋值给变量ptr。

那么,只要在变量ptr生存期所覆盖的范围内,程序都可以通过ptr来handle这块存储空间。问题在于,变量ptr单单属于函数qux治下。一旦函数qux例程完结,变量ptr即消亡。此时,若程序里没有任何变量ptr的遗子,程序将永远无法再handle那块存储空间,甚至无法释放它。那块存储空间将一直处于被占用(非空闲)状态,且是完全是个孤岛。这就造成了存储空间的浪费,是为“内存泄露”。

以上,仅供参考,呵呵 —— :)

P.S.: 这种技术概念性比较强的文章,还是请薛非大虾审订一下才是妥当。一旦通过了薛非大虾的审核,楼主再和盘接受就没有什么问题了,呵呵 ……



...全文
1027 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
hongjiean 2014-05-11
  • 打赏
  • 举报
回复
好的文章,但是新手表示还是有点难以听懂
天朗-星空 2014-05-05
  • 打赏
  • 举报
回复
引用 21 楼 zhao4zhong1 的回复:
太阳释放出来的一缕γ射线又不偏不倚地击穿了CSDN服务器的系统时钟!

计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构……

对学习编程者的忠告:
眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!

VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)

想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
但我又不得不承认:
有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物;
而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。
而我本人属前者。

不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质,
而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。


这辈子不看内存地址和内存值;只画链表、指针示意图,画堆栈示意图,画各种示意图,甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗?本人深表怀疑!
这辈子不种麦不收麦不将麦粒拿去磨面;只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头,压面条,烤面包,……能从本质上理解面粉、理解面食吗?本人深表怀疑!!

提醒:
“学习用汇编语言写程序”

“VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!

不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

有人说一套做一套,你相信他说的还是相信他做的?
其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗?

不要写连自己也预测不了结果的代码!

电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址;
人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……

十字链表交换任意两个节点C源代码(C指针应用终极挑战)http://download.csdn.net/detail/zhao4zhong1/5532495


&a运算后确实是指针,不过存到了eax寄存器,符合“由register限定的变量,有可能被编译器分配到寄存器。编译器被允许将变量分配到寄存器,从而弃绝可能发生的与该变量有关的“地址”与“指针”机制””,THX
天朗-星空 2014-05-05
  • 打赏
  • 举报
回复
引用 21 楼 zhao4zhong1 的回复:
太阳释放出来的一缕γ射线又不偏不倚地击穿了CSDN服务器的系统时钟!

计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构……

对学习编程者的忠告:
眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!

VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。
对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。
(Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)

想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。
从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单!
指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。”
但我又不得不承认:
有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物;
而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。
而我本人属前者。

不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质,
而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。


这辈子不看内存地址和内存值;只画链表、指针示意图,画堆栈示意图,画各种示意图,甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗?本人深表怀疑!
这辈子不种麦不收麦不将麦粒拿去磨面;只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头,压面条,烤面包,……能从本质上理解面粉、理解面食吗?本人深表怀疑!!

提醒:
“学习用汇编语言写程序”

“VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。
(Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
想要从本质上理解C指针,必须学习C和汇编的对应关系。”
不是一回事!

不要迷信书、考题、老师、回帖;
要迷信CPU、编译器、调试器、运行结果。
并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。
任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实!

有人说一套做一套,你相信他说的还是相信他做的?
其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗?

不要写连自己也预测不了结果的代码!

电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址;
人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息……

十字链表交换任意两个节点C源代码(C指针应用终极挑战)http://download.csdn.net/detail/zhao4zhong1/5532495

首先是很佩服老师,这些思想非常好,我在其他贴看到老师的回复也有很认真的读。

&a运算后确实是指针,不过存到了eax寄存器,符合“由register限定的变量,有可能被编译器分配到寄存器。编译器被允许将变量分配到寄存器,从而弃绝可能发生的与该变量有关的“地址”与“指针”机制””,THX
天朗-星空 2014-05-05
  • 打赏
  • 举报
回复
引用 20 楼 lin5161678 的回复:
[quote=引用 19 楼 yunfenglw 的回复:] 对于
(&(&a))
编译器为什么报错? 操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。&a结果因应该是一个指向a的指针变量b,而&b结果应该是另一个指向b的指针变量c,但为什么编译器对
(&(&a))[
报错:error C2102: '&' requires l-value
&a 结果应该是一个指向a的指针 然后 指针不等于指针变量 指针是一种数据类型 &&a 会报错的原因是 &a不是左值 [/quote] &a不是左值:不论左值、右值,计算机语言本质上是对存储空间的操作。int a;a=1;问你常量1”是左值还是右值?是把程序存储空间或者数据存储空间中常量“1”所在的存储单元数据传递给变量“a”所在存储空间。对于常量“1”所在的存储空间是读出操作,对变量“a”所在的空间是读出。
lin5161678 2014-05-05
  • 打赏
  • 举报
回复
引用 22 楼 yunfenglw 的回复:
&a 结果应该是一个指向a的指针[/color],也就是所&a的结果存储了“a”(或者说a变量地址),说明它是一个变量。“&&a”这样写不太合适, “&&”是“逻辑与”,写作(&(&a))
&a 没有存储a 1+2 结果是3 但是 1+2 绝对没有存储3 如果你无法放开所谓的指针是变量的想法 这帖子在说什么 你是无法理解的 这里说的指针 是一种数据类型
天朗-星空 2014-05-05
  • 打赏
  • 举报
回复
引用 20 楼 lin5161678 的回复:
[quote=引用 19 楼 yunfenglw 的回复:] 对于
(&(&a))
编译器为什么报错? 操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。&a结果因应该是一个指向a的指针变量b,而&b结果应该是另一个指向b的指针变量c,但为什么编译器对
(&(&a))[
报错:error C2102: '&' requires l-value
&a 结果应该是一个指向a的指针 然后 指针不等于指针变量 指针是一种数据类型 &&a 会报错的原因是 &a不是左值 [/quote]
引用 20 楼 lin5161678 的回复:
[quote=引用 19 楼 yunfenglw 的回复:] 对于
(&(&a))
编译器为什么报错? 操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。&a结果因应该是一个指向a的指针变量b,而&b结果应该是另一个指向b的指针变量c,但为什么编译器对
(&(&a))[
报错:error C2102: '&' requires l-value
&a 结果应该是一个指向a的指针 然后 指针不等于指针变量 指针是一种数据类型 &&a 会报错的原因是 &a不是左值 [/quote] &a 结果应该是一个指向a的指针,也就是所&a的结果存储了“a”(或者说a变量地址),说明它是一个变量。“&&a”这样写不太合适, “&&”是“逻辑与”,写作(&(&a))
赵4老师 2014-05-05
  • 打赏
  • 举报
回复
太阳释放出来的一缕γ射线又不偏不倚地击穿了CSDN服务器的系统时钟! 计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构…… 对学习编程者的忠告: 眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源代码千行不如单步对应汇编一行! VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。 (Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。) 想要从本质上理解C指针,必须学习汇编以及C和汇编的对应关系。 从汇编的角度理解和学习C语言的指针,原本看似复杂的东西就会变得非常简单! 指针即地址。“地址又是啥?”“只能从汇编语言和计算机组成原理的角度去解释了。” 但我又不得不承认: 有那么些人喜欢或者适合用“先具体再抽象”的方法学习和理解复杂事物; 而另一些人喜欢或者适合用“先抽象再具体”的方法学习和理解复杂事物。 而我本人属前者。 不要企图依赖输出指针相关表达式的值【比如printf("%p\n",...)】来理解指针的本质, 而要依赖调试时的反汇编窗口中的C/C++代码【比如void *p=...】及其对应汇编指令以及内存窗口中的内存地址和内存值来理解指针的本质。 这辈子不看内存地址和内存值;只画链表、指针示意图,画堆栈示意图,画各种示意图,甚至自己没画过而只看过书上的图……能从本质上理解指针、理解函数参数传递吗?本人深表怀疑! 这辈子不种麦不收麦不将麦粒拿去磨面;只吃馒头、吃面条、吃面包、……甚至从没看过别人怎么蒸馒头,压面条,烤面包,……能从本质上理解面粉、理解面食吗?本人深表怀疑!! 提醒: “学习用汇编语言写程序” 和 “VC调试(TC或BC用TD调试)时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 (Linux或Unix下可以在用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。) 想要从本质上理解C指针,必须学习C和汇编的对应关系。” 不是一回事! 不要迷信书、考题、老师、回帖; 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实! 有人说一套做一套,你相信他说的还是相信他做的? 其实严格来说这个世界上古往今来所有人都是说一套做一套,不是吗? 不要写连自己也预测不了结果的代码! 电脑内存或文件内容只是一个一维二进制字节数组及其对应的二进制地址; 人脑才将电脑内存或文件内容中的这个一维二进制字节数组及其对应的二进制地址的某些部分看成是整数、有符号数/无符号数、浮点数、复数、英文字母、阿拉伯数字、中文/韩文/法文……字符/字符串、汇编指令、函数、函数参数、堆、栈、数组、指针、数组指针、指针数组、数组的数组、指针的指针、二维数组、字符点阵、字符笔画的坐标、黑白二值图片、灰度图片、彩色图片、录音、视频、指纹信息、身份证信息…… 十字链表交换任意两个节点C源代码(C指针应用终极挑战)http://download.csdn.net/detail/zhao4zhong1/5532495
lin5161678 2014-05-05
  • 打赏
  • 举报
回复
引用 19 楼 yunfenglw 的回复:
对于
(&(&a))
编译器为什么报错? 操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。&a结果因应该是一个指向a的指针变量b,而&b结果应该是另一个指向b的指针变量c,但为什么编译器对
(&(&a))[
报错:error C2102: '&' requires l-value
&a 结果应该是一个指向a的指针 然后 指针不等于指针变量 指针是一种数据类型 &&a 会报错的原因是 &a不是左值
天朗-星空 2014-05-05
  • 打赏
  • 举报
回复
对于
(&(&a))
编译器为什么报错? 操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数。&a结果因应该是一个指向a的指针变量b,而&b结果应该是另一个指向b的指针变量c,但为什么编译器对
(&(&a))[
报错:error C2102: '&' requires l-value 是因为文中所说: “但是,请特别注意:前述规定的理由,并不是在于register跟寄存器有一一对应的联系!而是在于:由register限定的变量,有可能被编译器分配到寄存器。register这个限定符的真确含义是:可以用它,来提示编译器在为变量分配存储空间的时候,[color=blue]编译器被允许将变量分配到寄存器,从而弃绝可能发生的与该变量有关的“地址”与“指针”机制”??? 那不是又违背了:“操作符&:它用来帮助我们获取一个指针,这个指针仅指向它所作用的操作数”??
Roussell 2013-01-06
  • 打赏
  • 举报
回复
赞一个,谢谢
xiaoxiaozhu5 2013-01-05
  • 打赏
  • 举报
回复
受教了,十分感谢
naxiaoshenme2012 2013-01-05
  • 打赏
  • 举报
回复
好详细,顶一下。
DaiwjDev 2013-01-04
  • 打赏
  • 举报
回复
受教了,厉害啊
赵4老师 2013-01-04
  • 打赏
  • 举报
回复
不要迷信书、考题、老师、回帖; 要迷信CPU、编译器、调试器、运行结果。 并请结合“盲人摸太阳”和“驾船出海时一定只带一个指南针。”加以理解。 任何理论、权威、传说、真理、标准、解释、想象、知识……都比不上摆在眼前的事实! 对学习编程者的忠告: 眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源代码千行不如单步对应汇编一行!
lymoge 2013-01-04
  • 打赏
  • 举报
回复
好文!学习了。
mishisanyi 2013-01-04
  • 打赏
  • 举报
回复
受教了,
syscofield 2013-01-04
  • 打赏
  • 举报
回复
Irricht 2013-01-04
  • 打赏
  • 举报
回复
好文章,顶一下
wizard_tiger 2013-01-03
  • 打赏
  • 举报
回复
好详细,顶一下。
FancyMouse 2013-01-02
  • 打赏
  • 举报
回复
不是所有的lvalue都可以&……struct里的位域 有的不是lvalue的也可以&……函数名 嘛当然这些都只是edge case。
加载更多回复(5)

69,382

社区成员

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

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