指针,为何不能在全局作用域内申请内存??(兼某段C99标准的理解)

好吧。。首先得承认这应该是个较低级的错误,C老手估计不会犯这种错。。但我犯了。。

上个星期帮同学做个简单的控制台C程序,编译器为gcc,我在全局作用域中定义了指针变量并为其申请空间,满心以为这没什么问题,谁知编译的时候弹出了下面的错误:

initializer element is not constant

错误指向我定义全局指针并申请了内存空间的语句。时间较紧,我没有细想,上网搜了一下,结果是c99标准中全局变量和static静态变量的初始化必须使用常量表达式,而且指针不能在全局作用域内申请内存,当时我很疑惑,为何一定要常量表达式,想明白是函数体外不能写执行语句,于是照着改了过来,将申请内存放到函数中执行就O了,后头就没有再思考其背后的缘由了。

今天上午在教室看书(《程序员的自我修养》一书),看到编译二进制文件,程序装载的时候,我突然意识到上个星期这个问题估计同它们有关系,下完课后兴冲冲跑回寝室想一探究竟。

于是我写了个测试程序:

 1 #include <stdio.h>
2 #include <stdlib.h>
3
4 typedef struct _Image
5 {
6   int id;
7   struct _Image *next;
8 } *pImage, Image;
9
10 pImage image = (pImage)malloc(sizeof(Image));
11 int* i = (int*)malloc(sizeof(int));
12
13 //Image image;
14 //int i;
15
16 int main(void)
17 {
18   return0;
19 }

编译不出意外出现了“intializer element is not constant”的错误,将10,11行注释掉,将13,14行去注释,编译不再有错。

直觉是同编译后生成的二进制文件存储格式有关系。于是翻书,初步验证我的想法:

(我在Linux下编译的,二进制文件格式为ELF,windows虽然是PE也可参考,毕竟都是COFF标准嘛!)

编译器将源码编译为目标文件or二进制文件时,会将数据存放于存放于各个段中。按照ELF/COFF的标准,初始化了的全局变量和局部static变量存放于.data段,.bss段记录未初始化的全局变量和局部static变量。(注意,.bss不会存放数据,只用于为变量预留空间)。将10,11注释,13,14行去注释后编译所得的image和i变量存放与.bss段中,程序装载的时候会将.bss段映射到内存中并为该两变量开辟空间,整个过程是没有错的。反观,之前的代码(上面贴出的代码)编译后为何报错??因为image和i并没有使用常量表达式为其初始化。用常量表达式初始化,程序在编译的时候才能够顺利获得定义式右值(即我们给出的常量表达式)并将其值写入.data段中。举个例子,假如我这么为全局变量赋值:

int a =1;
int b = a;
//(注意:在全局作用域中)

此时编译肯定还是会报出之前同样的错误。全局变量a的赋值是没有错的,此时数据1顺利写入.data段中同a“绑定”,但是全局变量b的赋值却不尽人意了。为何?首先,b使用a变量进行初始化,但是b真能取到a的值吗??你也许会问,a不是已经赋值为1了吗??为神马不可以这么做??

有些人可能以一句话解释其中缘由:执行语句不可以置于函数体外。是的这没错,但是你想到没有为什么会是这样子??难道只是一个恶心的规定。哦不是的,任何事情总是有规律的我相信,规则的背后总还是有规则的存在。

对于这个问题,首先我们得明确编译的概念。编译是将程序代码转换为目标代码的过程。它是一个翻译的过程,不能执行程序。int b = a,这一句需要我们到内存中执行程序才能够实现,程序需要先取到a的值,再把该值复制到b对应的内存空间。但是现在我们是在编译耶!编译程序只将a的值写入.data并记录a符号之后就没有变量a什么事情了,b肯定是取不到a的值的!

好了,我们理解了那条标准制定的背后的原因,现在有关指针在全局作用域中申请内存空间错误的理解也就水到渠成了。

由“初始化了的全局变量和局部static变量存放于.data段,.bss段记录未初始化的全局变量和局部static变量”这句话可知,在我之前的程序中,我申请内存来初始化我的两个指针变量image和i,毫无疑问image和i应位于.data段上。定义指针没错,问题是,我现在能够申请到内存吗??不要以为这段代码是在程序装入到内存后执行的。实际上,因为编译器判定你是全局变量,此时它就是要你拿出常量来初始化它的变量好让它能往.data段中写数据,所以以编译器的逻辑你需要实实在在的从内存中申请到内存并把他们的值写入到.data段中。但实际可行吗??不可行。。且不说编译器不会帮你申请,程序需要在链接装载到内存后才会在虚拟内存中映射自己的堆栈段。。于是,这段代码夭折了,它理所当然的报错了。。

over,写blog总是不易的,何况我不能保证我的想法是不是完全正确的,这只是我的个人想法,不过起码它自园其说了哈哈,有些地方表达的估计不了解这块的人不清楚,原谅我吧,小弟文笔实在......

如果上面有什么错误请各位大佬严厉指出哈哈!!

posted @ 2011-09-16 00:09  cassvin  阅读(3078)  评论(16编辑  收藏  举报