不要有火*味

[复制链接]
 楼主| djyos 发表于 2008-2-24 09:56 | 显示全部楼层 |阅读模式
void&nbsp;fa(void)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;a;<br />&nbsp;&nbsp;&nbsp;&nbsp;fb();<br />}<br />void&nbsp;fb(void)<br />{<br />}<br />问题:fb函数为什么不能使用变量&nbsp;a。不要仅从C语言语法角度回答,重要的是从计算机实现方面来回答。
 楼主| djyos 发表于 2008-2-24 20:31 | 显示全部楼层

欢迎砸砖

&nbsp;&nbsp;&nbsp;&nbsp;首先,从逻辑上讲,让fb能访问变量a,是非常荒唐的,如果把fa称作fb的母函数,那么,谁能知道fb还有没有其他母函数呢?又谁能知道别的母函数定义了什么变量呢?是不是fb被多一个函数调用,它可访问的变量就多一些,各母函数独立的命名空间又从何谈起?<br />&nbsp;&nbsp;&nbsp;&nbsp;其次,从执行的角度,访问变量时,必须知道变量地址,全局变量的地址是硬编码在代码中的,局部变量的地址呢,是通过栈指针SP+偏移地址来访问的。函数入口处,sp值设为本函数局部变量的基地址,而编译器负责安排局部变量的偏移地址,故随着栈指针的变化,每次调用函数时,尽管局部变量的基址可能飘忽不定,但函数总能正确访问到。但每一个函数都只知道自己被调用时的SP,他们无从知道母函数的SP,所以,尽管母函数的变量已经定义且已分配了内存,但子函数根本无法访问母函数的局部变量。<br />&nbsp;&nbsp;&nbsp;&nbsp;但是,在特定环境中用“非常”手段,在函数fb中访问变量a,并非不可能,以gcc&nbsp;for&nbsp;arm为例,我们看看怎样用“非常”手段在fb中访问变量a的,大家看代码:<br />void&nbsp;fa(void)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;a=10;<br />&nbsp;&nbsp;&nbsp;&nbsp;fb();<br />}<br />void&nbsp;fb(void)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;*b&nbsp;=&nbsp;&b;<br />&nbsp;&nbsp;&nbsp;&nbsp;b&nbsp;+=5;&nbsp;&nbsp;&nbsp;&nbsp;//回退fb的参数空间和b变量本身所占空间<br />&nbsp;&nbsp;&nbsp;&nbsp;*b&nbsp;=&nbsp;0;&nbsp;&nbsp;&nbsp;//给变量a赋值。<br />}<br />&nbsp;&nbsp;&nbsp;&nbsp;利用“*b&nbsp;=&nbsp;0;”给a赋值的条件是:<br />1、fa和fb必须只有一个局部变量。<br />2、设置cpu在ARM状态而不是thumb状态,fa无外部调用。<br />3、要用gcc&nbsp;for&nbsp;arm4.10版本的编译器,其他编译器不敢包。<br />&nbsp;&nbsp;&nbsp;&nbsp;这说明,如果你清楚特定CPU和特定编译器的行为,用非常手段,fb中可以访问变量a。但是,这种访问不具备普遍性,是不可移植的。通过本例程得到的一个额外的收获是,我们知道了局部数组溢出是如何搞破坏的,CPU的栈向下生长的环境中,子函数数组上溢出时,最先遭殃的是本函数的局部变量,再往后就是母函数的局部变量,向下溢出时,会先破坏本函数的局部变量,再往下,只要不突破栈底,倒也不会有什么破坏力。<br />&nbsp;&nbsp;&nbsp;&nbsp;嵌入式编程与PC不同,PC有相对稳定的运行环境,跟硬件相关或者跟运行环境相关的部分,操作系统都已经替你封装好了;嵌入式环境却不同,嵌入式CPU种类繁多,即使相同的CPU,其存储器等周边配置也可能完全不同,因此,嵌入式程序员要时时考虑运行环境的问题。再者,嵌入式系统还要管理CPU的初始化和系统加载问题,这些又要求嵌入式程序员必须熟悉编译器和连接器的行为。<br />&nbsp;&nbsp;&nbsp;&nbsp;我们再来看看C语言和C编译器,众所周知,目前,嵌入式系统大多使用C语言开发,C语言并不是一门精确的语言,与其说C语言方便了C程序员,还不如说它方便了C编译器作者。早期C语言很多特性都是为了充分发掘CPU的潜力,使其便于实现,它方便了面对CPU的C编译器作者,却使C程序员一头雾水,同样的代码,却可能给出完全不同的执行结果,谁能告诉我,“int&nbsp;a&nbsp;=&nbsp;65536;”的执行结果是什么?虽然ansi&nbsp;c做了很多努力,但就是无法根本改变C语言这一固有特征。付出了执行结果不确定的代价之后,C获得的好处就是几乎可以实现任何汇编可以实现的功能,并且效率很高。C语言的限制,实际上就是现阶段CPU的通用限制,除操作CPU寄存器必须用汇编外,C语言所不能实现的,基本上普天之下的CPU都难于用汇编实现。SP是函数访问局部变量的基础,CPU在调用函数时,随着调用深度的增加,栈指针SP将不断移动,调用子函数时,母函数的SP必须保存,以备从子函数中返回时恢复SP。通用CPU的函数调用深度理论上只受内存空间的限制,不可能有专用硬件来保存SP,所以各级母函数的SP,就保存在栈中,母函数自己知道保存在哪里,但子函数却不知道母函数的SP保存在哪里,所以根本无法访问母函数的局部变量,这就是从计算机实现的角度理解,fb不能访问变量a。谁要是能写出一个能让子函数自由访问母函数变量的汇编程序来,我下马拜师!<br />&nbsp;&nbsp;&nbsp;&nbsp;所以,了解C编译器,就等于了解各种嵌入式CPU的共性,根据C的语法限制,C语言是不可能保证实现在函数fb中访问变量a的,我们不但要知道有这个限制,还要知道为什么有这个限制。<br />
computer00 发表于 2008-2-24 20:58 | 显示全部楼层

我再来说两句

首先,变量a不一定就被分配到了栈中,有可能是固定的地址(例如C51),也可能是CPU的工作寄存器(很多编译器将少量的<br />局部变量放在工作寄存器中),当然也可能是堆栈。<br /><br />①、首先假设是分配在固定地址的(编译器当然知道这个固定地址是多少了),那么楼主的问题好解决,编译器在其它函数也同样<br />访问该地址即可。<br /><br />②、假设是分配在CPU的工作寄存器的,这个问题也好解决,同上,直接访问那个工作寄存器即可。<br /><br />③、假设这个局部变量是分配在栈中的。那么编译器当然知道自己做了多少次压栈操作,然后根据压栈的次数以及当前栈<br />指针,直接计算就可以得到那个地址。至于有多个函数调用的问题,在编译时通过对代码扫描,分析调用关系,应该也能<br />生成正确的代码。至于13楼所说的问题完全可以避免,栈的本质是内存,所以没有必要将其它都弹出,即便是弹出了,也<br />无所谓,内存有个特性就是可以多次读操作,多弹几次出来无所谓,只要你不要修改栈指针即可。<br /><br />从上面的分析我们可以看出,问题的焦点在于代码如何生成,而不是CPU执行问题,也不是计算机结构问题,所以楼主的问题<br />要求从计算机实现角度来理解是难以理解的。
rongzhai 发表于 2008-2-24 10:21 | 显示全部楼层

sf

局部变量是分配在堆栈中的.
wuji2005 发表于 2008-2-24 11:08 | 显示全部楼层

精辟

在理
HWM 发表于 2008-2-24 12:23 | 显示全部楼层

这能说明有一定嵌入式系统开发功力了?扯蛋!

还是看一下C++内有何奥妙吧,其中够你玩的。
computer00 发表于 2008-2-24 12:53 | 显示全部楼层

跟计算机无关,就是因为C语言编译器规定了不能调用它

如果编译器真的想调用它,那还不是轻而易举的事情...
HWM 发表于 2008-2-24 13:20 | 显示全部楼层

这和编译器的关系并不是很大。

虽然形式上看起来fb()的执行处在fa()的范围内,但不能保证在fa()外不存在对fb()的调用。若存在fa()外对fb()的调用,这时a是无定义的(可能根本就还没被分配空间),如此又如何可以在fb()内使用a呢?<br />
 楼主| djyos 发表于 2008-2-24 13:21 | 显示全部楼层

呵呵

&nbsp;&nbsp;&nbsp;&nbsp;2、3楼说得没错,但不全面,局部变量在栈中分配没错,但fa调用fb时,fa和fb两个函数的局部变量都是在同一个栈中分配的,而且,fb的整个执行过程中,变量a已经分配而且持续存在,为何不能使用呢?<br />&nbsp;&nbsp;&nbsp;&nbsp;4楼,不想跟你争。<br />&nbsp;&nbsp;&nbsp;&nbsp;5楼,编译器是忠实于语法的,题设中说了不要仅从语法角度分析。
HWM 发表于 2008-2-24 13:26 | 显示全部楼层

LZ:对问题的理解不能太片面,语法自有其存在的道理。

  
xwj 发表于 2008-2-24 14:22 | 显示全部楼层

真是扯蛋!lz 先搞清楚各种变量的作用域再说吧

不管什么编译器,也不管什么CPU,更不用管它是怎么去管理、分配内存、怎么实现这个限制的,<br /><br />C编译器为了保证语法、规则合乎ANSI&nbsp;C,都得想方设法去保证作用域的正确,否则就不是合格的编译器了
phoenixmy 发表于 2008-2-24 14:52 | 显示全部楼层

复杂

<br /><br />莫不成还要看编译原理啥的<br /><br />
computer00 发表于 2008-2-24 19:11 | 显示全部楼层

这本来就是人们自己定义的,局部变量在其它函数不得访问

如果我修改下定义,局部变量在其它函数内也可以访问,难道这样一个“C”编译器还做不出来了?<br /><br />但是哪有又什么意义?所以不能从计算机角度去理解,而应该从更适合编程去理解。
xwj 发表于 2008-2-24 19:19 | 显示全部楼层

说到底,LZ的要求不是实现不了,而是规则上坚决不允许实现

否则那就是个错误的、有BUG的C编译器
happystar 发表于 2008-2-24 20:14 | 显示全部楼层

re 大家看我说的对不

void&nbsp;fa(void)<br />{<br />1&gt&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;a;<br />2&gt&nbsp;&nbsp;&nbsp;&nbsp;fb();<br />}<br />void&nbsp;fb(void)<br />{<br />}<br />当程序执行到语句1&gt后则堆栈上分配一个2个字节的变量,如下所示:<br />|&nbsp;&nbsp;&nbsp;|<br />|&nbsp;a&nbsp;|<br />|...|<br />|___|<br />当程序执行到语句2&gt后则堆栈吧fa的现场再次压入堆栈,如下图:<br />|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />|fa的现场|<br />|&nbsp;&nbsp;&nbsp;a&nbsp;&nbsp;&nbsp;&nbsp;|<br />|........|<br />|________|<br />接下来在执行fb函数的时候,如果需要a,那么需要从堆栈中把a取出来。但是在堆栈的结构中a在fa的现场之下,也就是取a的值必须先把fa的现场恢复起来,也就是说执行fb如果调用a,那么fa的现场也破坏了,这就产生错误。<br />
sinanjj 发表于 2008-2-24 20:21 | 显示全部楼层

fb函数为什么不能使用变量 a??

个人认为:&nbsp;是因为a前边没有加extern
sinanjj 发表于 2008-2-24 20:28 | 显示全部楼层

happystar 的解释确实让人佩服,

那为什么还可以声明a为全局变量呢?&nbsp;全局变量放哪了??<br /><br />请赐教&nbsp;(当上编译原理课了,&nbsp;注:&nbsp;本人还没学过编译原理课,&nbsp;答错了题情有可原,&nbsp;哈哈)
sinanjj 发表于 2008-2-24 21:02 | 显示全部楼层

把这贴保存了, 好好研究研究先

原来只重视应用,&nbsp;底层的东西不太管,&nbsp;djyos这么一说,&nbsp;还真是受益匪浅.<br /><br />研究研究,学习学习先.<br /><br />&quot;嵌入式环境却不同,嵌入式CPU种类繁多,即使相同的CPU,其存储器等周边配置也可能完全不同,因此,嵌入式程序员要时时考虑运行环境的问题。&quot;<br />--------------确实是这样的.&nbsp;有必要深入一下.
happystar 发表于 2008-2-24 21:11 | 显示全部楼层

djyos说的对

但每一个函数都只知道自己被调用时的SP,他们无从知道母函数的SP,所以,尽管母函数的变量已经定义且已分配了内存,但子函数根本无法访问母函数的局部变量。<br />
 楼主| djyos 发表于 2008-2-24 21:31 | 显示全部楼层

圈圈果然见多识广

&nbsp;&nbsp;&nbsp;&nbsp;圈圈所说的,都是实际可能存在的情况,我在15楼也举了一个fb中访问变量a的例子,针对C51这样在堆中保存局部变量的编译器,也可以写出一个这样的例程来的,针对圈圈列出的其他假设也一样,但无一例外,都要求“你清楚特定CPU和特定编译器的行为,用非常手段”,题设中并没有规定什么编译器,也没有说明用什么CPU。所以,还是那句话,“C的语法限定,在很多时候是依赖于通用CPU是否能实现来的”,深入研究嵌入式C语言,就必须了解计算机实现原理。这于PC编程大不一样,PC中并不是在研究C语言,而是在研究库函数和操作系统的系统调用。<br />&nbsp;&nbsp;&nbsp;&nbsp;至于编译器扫描分析代码的确定调用关系是不行的,函数指针怎么办?<br />&nbsp;&nbsp;&nbsp;&nbsp;
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

454

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部