【例14.21】典型变量存储地址分配演示。
#include <stdio.h> #include <stdlib.h> int a ; static int b ; char ch ; // 没有初始化 int c=10 ; char s1=\"OKwe\" ; char c1=\'w\' ; // 初始化 const int i=25 ; // 全局常量 char *p=\"We are here !\" ; // 全局常量 void ttt () // 函数 { printf (\" 结束n\" ); } int main ( ) { int num=0 ; const int n=25 ; volatile int result=15 ; static int m=15 ; int *p1 , *p2 ; char *pc=\"She is here !\" ; char st=\"We are here !\" ; p2= (int * )malloc (100 ); // 分配堆 p1=&num ; printf (\" 全局没初始化:t%p %p %pn\" ,&a ,&b ,&ch ); printf (\" 全局初始化:t%p %p %pn\" ,&c ,&c1 ,&s1 ); printf (\" 全局常量:t%p %pn\" ,&i ,&p ); printf (\" 局部常量:t%p %p %p %pn\" ,&n ,pc ,p2 ,&m ); printf (\" 局部变量:t%p %p %p %p %p %pn\" ,&num ,&result ,p1 ,&pc ,&p1 ,&p2 ); printf (\" 两种常量:t%p %p %p %pn\" ,p ,&p , pc ,&pc ); printf (\" 局部字串:t%p %p %p %pn\" ,st ,&st , pc ,&pc ); printf (\" 函数地址:t%p %pn\" ,&main ,&ttt ); return 0 ; }
程序输出结果如下:
全局没初始化: 004237C8 004237CC 004237D0 全局初始化: 004232F8 00423301 004232FC 全局常量: 00420F2C 00423304 局部常量: 0012FF78 0042001C 00430070 00423308 局部变量: 0012FF7C 0012FF74 0012FF7C 0012FF68 0012FF70 0012FF6C 两种常量: 00420F7C 00423304 0042001C 0012FF68 局部字串: 0012FF58 0012FF58 0042001C 0012FF68 函数地址: 0040100A 0040100F
可以把存储区分为代码区、文字常量区、全局区(静态区)、堆和栈。代码区用来存放程序的二进制代码,由系统负责。文字常量区存放字符串常量。这一点要特别注意,不管是全局字符串常量,还是局部字符串常量,都由系统分配在文字常量区,这个区位于全局区,当然,局部字符串常量只是存储在文字常量区,并不像全局字符串那样可以共享。主程序的字符串常量pc就是这种情况,它是局部常量,但系统并不将它分配在栈区,虽然分配在全局区(存储在0042001C),但又不能像全局字符串常量p(存储在00423304)那样提供共享。区别是指针常量p的&p被分配在全局区,指针pc的&pc被分配在栈区。程序中将它们单独输出出来(见第6行输出)供对比。局部字串一行将st和pc进行比较,通过对这些常量和变量的分配方式,能提高编程的效率,这将在后面专门叙述。
注意:本程序的文字常量和符号常量(const)是相隔较远的(见第3行输出)。其实,const申明的变量有时候根本不存在,全部在编译的时候被替换成具体的值,即使声明的变量存在,也不在文字常量区。文字常量区仅用于保存字符串常量。
全局区(静态区)是存放全局变量和静态变量的存放区域,分为已初始化和未初始化两个区域,已初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。对比第1行和第2行的结果即知。注意第3行i和p的分配地址以及p和c的分配地址,就可以进一步理解文字常量的含义。其实,我们又把编译器处理全局变量所存储的区域称作数据区,“=”号并不作为赋值语句,仅作为初始值。函数的地址也是放在数据区,与全局变量存放的区域接近。
堆区(heap)一般由程序员分配和释放。如果程序员没有释放,则在程序结束后,由操作系统收回。程序为p2分配的地址不在栈区,是堆区。
栈区由编译器自动分配和释放,存放函数的参数、局部变量等。在函数里面,除了文字常量之外,其他的局部常量与局部变量都一样,均由系统分配在栈区,程序结束后由系统释放。这些都是函数调用的基础,后面将常常涉及这些知识。
也把局部变量分配的区域称为程序区,将“=”号解释为一条赋值指令,所以分配在栈上。将它声明为局部变量,并跟声明为全局变量相比,则增加了指令数量,但减少了数据量。
必须注意,系统对不同数据类型采取的存储方法不一样。因为联合的元素使用同一地址,所以可以使用联合演示一下对同一地址的内容采取不同类型输出的结果。
【例14.22】演示同一数据使用不同数据类型的结果。
#include <stdio.h> union uda { int num ; unsigned char str[4] ; float f ; }u ; void main ( ) { int i=0 ; u.f = 1.0 ; printf (\"f = %fn\" , u.f ); for (i=0 ; i<4 ; i++ ) printf (\"str[%d] = %#xn\" , i , u.str[i] ); printf (\"num= %#xn\" , u.num ); u.f = 0.0 ; printf (\"f = %fn\" , u.f ); for (i=0 ; i<4 ; i++ ) printf (\"str[%d] = %#xn\" , i , u.str[i] ); u.num = 0x3f800000 ; printf (\"num= %#xn\" , u.num ); printf (\"f = %fn\" , u.f ); }
程序中先设置u.f=1,通过输出u.str的内容
str[0]=0 str[1]=0 str[2]=0x80 str[3]=0x3f
可以知道它在内存的存储方式为0x3f 0x80 0x 00 0x00。这时的u.num也是这个地址,因此它的值应该为0x3f800000。
可以通过先设置u.num,再打印u.f来验证这一点。为了更有说服力,先通过u.f=0.0将这个内存的内容置为0,然后使用
u.num = 0x3f800000 ;
语句设置这段内存,这时u.f的内容应该为1.0,strd的内容为0 0 0x80 0x3f。对照下面的输出结果,验证这个结论。
f = 1.000000 str[0] = 0 str[1] = 0 str[2] = 0x80 str[3] = 0x3f num= 0x3f800000 f = 0.000000 str[0] = 0 str[1] = 0 str[2] = 0 str[3] = 0 num= 0x3f800000 f = 1.000000 str[0] = 0 str[1] = 0 str[2] = 0x80 str[3] = 0x3f
由此可知,使用中一定不能混淆数据类型。