打印

不要有火*味

[复制链接]
7042|46
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
djyos|  楼主 | 2008-2-24 09:56 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
void fa(void)
{
    int a;
    fb();
}
void fb(void)
{
}
问题:fb函数为什么不能使用变量 a。不要仅从C语言语法角度回答,重要的是从计算机实现方面来回答。

相关帖子

来自 2楼
djyos|  楼主 | 2008-2-24 20:31 | 只看该作者

欢迎砸砖

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

使用特权

评论回复
来自 3楼
computer00| | 2008-2-24 20:58 | 只看该作者

我再来说两句

首先,变量a不一定就被分配到了栈中,有可能是固定的地址(例如C51),也可能是CPU的工作寄存器(很多编译器将少量的
局部变量放在工作寄存器中),当然也可能是堆栈。

①、首先假设是分配在固定地址的(编译器当然知道这个固定地址是多少了),那么楼主的问题好解决,编译器在其它函数也同样
访问该地址即可。

②、假设是分配在CPU的工作寄存器的,这个问题也好解决,同上,直接访问那个工作寄存器即可。

③、假设这个局部变量是分配在栈中的。那么编译器当然知道自己做了多少次压栈操作,然后根据压栈的次数以及当前栈
指针,直接计算就可以得到那个地址。至于有多个函数调用的问题,在编译时通过对代码扫描,分析调用关系,应该也能
生成正确的代码。至于13楼所说的问题完全可以避免,栈的本质是内存,所以没有必要将其它都弹出,即便是弹出了,也
无所谓,内存有个特性就是可以多次读操作,多弹几次出来无所谓,只要你不要修改栈指针即可。

从上面的分析我们可以看出,问题的焦点在于代码如何生成,而不是CPU执行问题,也不是计算机结构问题,所以楼主的问题
要求从计算机实现角度来理解是难以理解的。

使用特权

评论回复
地板
rongzhai| | 2008-2-24 10:21 | 只看该作者

sf

局部变量是分配在堆栈中的.

使用特权

评论回复
5
wuji2005| | 2008-2-24 11:08 | 只看该作者

精辟

在理

使用特权

评论回复
6
HWM| | 2008-2-24 12:23 | 只看该作者

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

还是看一下C++内有何奥妙吧,其中够你玩的。

使用特权

评论回复
7
computer00| | 2008-2-24 12:53 | 只看该作者

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

如果编译器真的想调用它,那还不是轻而易举的事情...

使用特权

评论回复
8
HWM| | 2008-2-24 13:20 | 只看该作者

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

虽然形式上看起来fb()的执行处在fa()的范围内,但不能保证在fa()外不存在对fb()的调用。若存在fa()外对fb()的调用,这时a是无定义的(可能根本就还没被分配空间),如此又如何可以在fb()内使用a呢?

使用特权

评论回复
9
djyos|  楼主 | 2008-2-24 13:21 | 只看该作者

呵呵

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

使用特权

评论回复
10
HWM| | 2008-2-24 13:26 | 只看该作者

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

使用特权

评论回复
11
xwj| | 2008-2-24 14:22 | 只看该作者

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

不管什么编译器,也不管什么CPU,更不用管它是怎么去管理、分配内存、怎么实现这个限制的,

C编译器为了保证语法、规则合乎ANSI C,都得想方设法去保证作用域的正确,否则就不是合格的编译器了

使用特权

评论回复
12
phoenixmy| | 2008-2-24 14:52 | 只看该作者

复杂



莫不成还要看编译原理啥的

使用特权

评论回复
13
computer00| | 2008-2-24 19:11 | 只看该作者

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

如果我修改下定义,局部变量在其它函数内也可以访问,难道这样一个“C”编译器还做不出来了?

但是哪有又什么意义?所以不能从计算机角度去理解,而应该从更适合编程去理解。

使用特权

评论回复
14
xwj| | 2008-2-24 19:19 | 只看该作者

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

否则那就是个错误的、有BUG的C编译器

使用特权

评论回复
15
happystar| | 2008-2-24 20:14 | 只看该作者

re 大家看我说的对不

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

使用特权

评论回复
16
sinanjj| | 2008-2-24 20:21 | 只看该作者

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

个人认为: 是因为a前边没有加extern

使用特权

评论回复
17
sinanjj| | 2008-2-24 20:28 | 只看该作者

happystar 的解释确实让人佩服,

那为什么还可以声明a为全局变量呢? 全局变量放哪了??

请赐教 (当上编译原理课了, 注: 本人还没学过编译原理课, 答错了题情有可原, 哈哈)

使用特权

评论回复
18
sinanjj| | 2008-2-24 21:02 | 只看该作者

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

原来只重视应用, 底层的东西不太管, djyos这么一说, 还真是受益匪浅.

研究研究,学习学习先.

"嵌入式环境却不同,嵌入式CPU种类繁多,即使相同的CPU,其存储器等周边配置也可能完全不同,因此,嵌入式程序员要时时考虑运行环境的问题。"
--------------确实是这样的. 有必要深入一下.

使用特权

评论回复
19
happystar| | 2008-2-24 21:11 | 只看该作者

djyos说的对

但每一个函数都只知道自己被调用时的SP,他们无从知道母函数的SP,所以,尽管母函数的变量已经定义且已分配了内存,但子函数根本无法访问母函数的局部变量。

使用特权

评论回复
20
djyos|  楼主 | 2008-2-24 21:31 | 只看该作者

圈圈果然见多识广

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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

454

帖子

1

粉丝