打印

编译器对作为局部变量的数组是怎么管理的?放在堆栈中?

[复制链接]
9685|41
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
xlander|  楼主 | 2008-1-2 11:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
沙发
db10| | 2008-1-2 12:55 | 只看该作者

。。。

C语言的堆跟栈是有区别的,请大家不要混淆。
局部变量和函数调用时的实参是放在栈里。所以有大家常说函数调用时的入栈,出桟这个说话。
动态申请的内存放在堆里的。
全局变量和静态变量是放在另外的全局内存区。

使用特权

评论回复
板凳
xlander|  楼主 | 2008-1-3 08:23 | 只看该作者

太感谢了,明白了

使用特权

评论回复
地板
ayb_ice| | 2008-1-3 15:43 | 只看该作者

这取决于编译器

使用特权

评论回复
5
db10| | 2008-1-3 16:04 | 只看该作者

是的

 跟编译器有一定关系。我上面说的一般指GCC,51编译器。
 ayb_ice 说说一般市面上常见的编译器对堆跟桟的处理方式吧。。。

使用特权

评论回复
6
flanker| | 2008-1-3 21:35 | 只看该作者

对,所以最好不要在局部变量里面开大数组。

如果操作系统支持的任务栈较小,没准就把栈写爆了。
我们现在弄的系统,任务栈默认是64K,从前编程规范没有规定不许在局部变量里面开大数组,于是发生过无数写爆调用栈的惨案。
这种问题很难定位,因为任务栈写坏了,系统异常时的Dump Memorycore通常已经没有任何价值,而且EPC等寄存器往往也写错了,只能走读代码来查这些问题。
后来新的规范里面规定了,局部变量不许超过1KB,如果有需求,用malloc申请动态内存。但这又会容易内存泄漏,特别是异常退出分支容易忘记释放。
内存泄漏的问题更加恐怖,比如系统运行三天三夜,处理了1000...000个报文以后,自己重启了。查这种问题,往往害得大家在公司打地铺睡觉。

使用特权

评论回复
7
ayb_ice| | 2008-1-4 08:43 | 只看该作者

KEIL C51 ,PICC这些都使用固定地址

局部变量也是固定地址,但编译器会自动分析函数的调用关系,这样很多局部变量不会同时使用,可以使用同一地址(变量),这些都是由编译器自动完成...
因为如此这种编译器不推荐使用函数指针,也不能实现函数重入,但这些限制对简单嵌入式系统不成问题,所以KEIL C51 ,PICC都是业界公认的高效编译器,在51,PIC是应用广泛...
而FSL的CW则是标准的C,C++编译器,局部变量是通过堆栈分配的,也好用...

使用特权

评论回复
8
computer00| | 2008-1-4 09:28 | 只看该作者

C51可以加reentrant关键词让函数可重入...

不过这时变量也不是分配在硬件栈中,而是一个编译器维持的软件栈...

在C51下,如果很多函数没有被调用,编译器就无法分析出那些的调用关系,结果就会造成RAM大量浪费...

使用特权

评论回复
9
sz_kd| | 2008-1-4 09:47 | 只看该作者

学习了

使用特权

评论回复
10
xlander|  楼主 | 2008-1-4 15:03 | 只看该作者

那ADS1.2的局部变量放在那里了

我用的是ARM,环境是ADS1.2.

尤其是大数组。

干脆出几个例子好了。
char c;
char d[1000];
void fun1()
{
static char a[1000];
static char b;
//...
}

上面的a,b,c,d是怎么分配的,都放在那里?哪个是堆,哪个是栈?

使用特权

评论回复
11
db10| | 2008-1-5 17:23 | 只看该作者

xlander

ADS1.2 的具体编译分配如我上面说的。

我记得几年前还在广州上班时,碰到个一个现在,你是51的程序,整个程序只要去掉一个局部变量程序就跑得正常,多一个就跑得混乱。当时查这个问题公司4个人搞了一个星期。
 去年我用linux得时候,应用程序里由于有队列,局部变量申请数据太多,导致程序跑得混乱。这都是我亲自碰到过得。不过当时我很快就查到原因所在。其实linux用户级桟我**中是2M,也挺大得,当嵌套调用多,而且数组大,也是有问题出现得。

flanker 说:后来新的规范里面规定了,局部变量不许超过1KB,如果有需求,用malloc申请动态内存。但这又会容易内存泄漏,特别是异常退出分支容易忘记释放。
 这个是个很好得方法,俺非常赞成。

flanker 说的:后来新的规范里面规定了,局部变量不许超过1KB,如果有需求,用malloc申请动态内存。但这又会容易内存泄漏,特别是异常退出分支容易忘记释放。
其中“但这又会容易内存泄漏,特别是异常退出分支容易忘记释放”,这个00比较有一手,自己写内存管理程序,完全自己可以控制,我是去年从00那学的,发现很好用。因为去年我用ADS调试程序时,发现malloc和free 出现问题,有时malloc申请不到,不稳定,后来就看到00的,效仿他的,发现问题解决了,现在我是用ARM,linux的应用程序内存管理这块全是自己写的内存管理程序,不用那让人不放下,不可控的malloc了。

其实在51的keil里用malloc也是有技巧,不是简单的调用malloc就可以。
 呵呵,这些东西,让我写,还真的能写出不少感受,因为以前做项目经历的bug太多。

使用特权

评论回复
12
gliethttp| | 2008-1-5 17:36 | 只看该作者

我的理解

1.static和全局量都在物理内存中分配,
2.局部变量或者被编译器分配到寄存器r0~r12或者在sp的栈空间中,比如:
c001503c <init>:
c001503c:    e1a0c00d     mov    ip, sp
c0015040:    e92dd800     stmdb    sp!, {fp, ip, lr, pc}
c0015044:    e24cb004     sub    fp, ip, #4    ; 0x4
c0015048:    ebffcdca     bl    c0008778 <do_basic_setup>
上边的fp就是在栈sp-4的空间分配的一个局部变量存储空间
3.都是和cpu紧密相关的硬件栈,没有软件栈

使用特权

评论回复
13
flanker| | 2008-1-6 21:23 | 只看该作者

自己做超越操作系统的二级内存管理,也是个不错的办法

有的时候OS的内存管理不大好使。
申请了若干小块内存,再申请大块内存可能会申请不到。这时候申请的次序就得有讲究了。
在系统性能的关键路径,比如网络转发,往往用快速Buf,就是一个二级/三级内存管理的机制,超越操作系统之上的内存管理。当然,这样做在软件设计/编码阶段的工作量会大不少,因此还需要衡量利弊。
对内存泄漏的问题,目前有BounsChecker、PC-Lint等工具都可以检查出来的。
BounsChecker是对malloc/free、new/delete进行打桩;
PC-Lint是一个很严格的编译器,可以报告很多程序的隐患。(当然,误报也相当多)
这俩东西都挺好使的,如果做工程的话可以考虑学习一下。

使用特权

评论回复
14
flanker| | 2008-1-6 21:41 | 只看该作者

看来computer00兄弟是个高手呀,不得不佩服

C51使函数可以重入的办法,我3年前玩儿MCS51的时候,痛苦地摸索了很久才弄明白。其实Keil里面的局部变量全都是static,和全局变量本质没有两样。
51的内部ram实在太小了,螺蛳壳里做道场的感觉有时候也挺好玩的。
后来接触了44B0处理器和EPSON C33两种32位的处理器,发现32位是一个新天地,也是以这些知识作为敲门砖,敲开了现在公司的大门,有幸跟着几位**师学业界先进的技术。
现在说到嵌入式就是ARM,实际上嵌入式系统的水很深,ARM/PPC/MIPS/x86都有自成一套的体系结构,搞明白一种就衣食无忧,如果精通两三种并在技术之外也有较好的修养,则能过上宝马香车的日子。
一般玩的ARM7/ucLinux是No MMU的处理器吧。有了MMU的处理器,如x86,又有了虚实地址的概念,简单说就是程序里面的地址和总线上用逻辑分析仪抓到的地址是不一样的。x86的MMU玩熟悉了,就能成为CIH病毒陈英豪那样的高手——只要情商别像它那样,就能成为绝对的成功人士啦。
唉,灌水又多说了几句。

使用特权

评论回复
15
computer00| | 2008-1-6 23:48 | 只看该作者

哪里,俺只玩些简单的,复杂一点的不敢动,伤脑筋啊,怕短寿

使用特权

评论回复
16
db10| | 2008-1-7 09:05 | 只看该作者

00

 主要是你还在读书,没有上班,所以真实复杂项目也许会少点,但是你的基本功还是很扎实,这是最重要的,呵呵。
 以前在华为呆了陈子,那时的一个项目,CPU是32个内核,同时跑好几个系统。。。 当然同时做这个项目的人也是一大把。 不过再复杂的项目,也是由一个一个组成,而每个项目中也就是那些基础的了。

使用特权

评论回复
17
flanker| | 2008-1-8 23:36 | 只看该作者

32内核,多核多系统

我们也在搞。
同意db10,基本功很重要,学好以后,学高级的东西飞快。

使用特权

评论回复
18
db10| | 2008-1-9 15:16 | 只看该作者

。。。。

flanker  你在3com?

使用特权

评论回复
19
flanker| | 2008-1-10 21:39 | 只看该作者

信息安全,重于泰山,
在这里多嘴,HR姐姐抓到要打PP的。
我想基本功就是这些东西吧:
中断、任务、信号量和队列;
指令集,流水线,Cache和MMU;
链表/二叉树/Hash表...;

当然,对于做底层驱动来说,前面几样更重要...

使用特权

评论回复
20
flanker| | 2008-1-10 21:49 | 只看该作者

对了,说正题

函数传参的时候,并不是所有参数都放在栈里面的。
x86不大熟悉,好象是逐个入栈;
MCS51是用R4~R7传参,如果放不下则入栈;
mips是用a0/a1/a2/a3传参,放不下的入栈;
PPC则有8个通用32位寄存器传参数;

总之,risc的CPU,往往不支持硬件栈,但有多个通用寄存器传参;
cisc的CPU,往往支持硬件栈,但不用寄存器传参。
算起来,函数调用的开销,其实还是差不多的,因为实际上入参大于4个的函数并不多。如果真的有这么多,往往就设计为结构体了。

用栈传参又有C约定和PASCAL约定两种,C约定是右边参数先入栈,调用者恢复堆栈指针,而PASCAL约定是左边参数先入栈,被调用者恢复堆栈指针。
C约定有个特别的好处,就是支持变参函数。
咱们知道printf函数的原型是这样的:
unsigned int printf (char *fmt, ...);

这样,有了第一个确定的参数,知道了堆栈的顶部在哪里,就可以确定后面参数的个数了。
——因此,Windows下其他API都是PASCAL约定与C约定的混合,叫STDCALL,只有wsprintf函数用标准C调用约定。

从前的Win3.x下调用API都是PASCAL约定,感觉很别扭。

使用特权

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

本版积分规则

65

主题

165

帖子

0

粉丝