C编译器内存分配
我们知道,可以用printf(“%p”,xx)来打印出变量的地址(虚拟地址),因此可以借此窥探变量在内存中的分配。
首先谈谈什么是虚拟地址,操作系统会给每个进程分配一个虚拟地址,这和C语言没有关系,而是操纵系统和CPU共同努力的结果。这样,就算程序产生了一个bug,破坏了内存区,也不会影响其他进程的操作。
下面是进入正题。下面的程序试图打印出全局变量,文件内静态变量,函数入口地址,字符串的地址,通过malloc分配的内存,函数内部静态变量和函数内部变量,
结果是:
可以看出函数内部的变量离其他变量比较远。
函数入口地址; 字符串常量 | 0x56405 44da xxx |
全局变量,静态变量(归为静态变量) | 0x56405 46db xxx |
用malloc分配的内存地址 | 0x56405 53c1 xxx |
可以看出,表格中的三项离得比较近,但是却仍有距离。(毕竟16进制)
但相对于函数内部变量,也就是动态变量,即auto类型的变量,还是算近的。
再来看看区别:下面打出main函数里的auto类型变量和静态变量
发现auto类型的变量彼此都离得很近。
而且,仔细看会发现,func2_var的地址竟然和func1_var的地址是相同的。
那么先来说说:函数入口地址和字符串常量的地址内存吧
因为函数和字符串本身在运行程序的时候是不可能改写的,所以它被分配到内存的只读区域。
虽然说很久以前函数是可以改写的,但会把程序弄得晦涩难懂,因此现代大多数操作系统都禁止了。
字符串常量就不用说了,常量嘛肯定不能修改。这也就是为什么当一个指针指向一个字符串时,其所指向的值是不可修改的。如
char *p = "I am a student";
*(p+2) = 'b';
这是错误的,在linux中会报出段内存错误。因为指针指向的是只读内存,只读内存不能修改。
但修改指针的值:p = &a,就肯定是正确的。这里不再解释。
下面来看看:静态变量
静态变量包括:静态局部变量、全局变量 和 文件内静态变量。
在大多数的书中都写到,静态变量在程序开始运行的时候就被加载到内存中了。
从上面的运行结果上看,静态变量们都离得很近。
我们知道,一个C源代码(当然可以是多个)要想编译成可执行文件,必须经过预处理、汇编、编译和链接,在编译的时候会生成一个.o的目标文件,多个目标文件链接生成一个可执行文件。
关键就在链接这一步,对于静态变量来说,在跨越不同目标文件时会被当成同一个对象对待。对局部变量则相反,即使不同源文件的局部变量用了相同的名称,在同一个程序中也会被区别对待。
于是对静态变量来说,为了方便将名称和静态变量结合起来,各目标(.o)文件都具备一个符号表,linux中可以使用nm来窥视他们。
如图。可以看出静态变量,和函数入口地址全在里边。但局部变量呢?没有是吧。
连接器就根据这个符号表,给这些变量们分配内存。因此,静态变量在程序执行开始的时候就分配了内存,这句话是正确的。
最后在来看看 auto类型的变量
从运行结果上看func2_var的地址和func1_var的地址是相同的。这就表明auto变量的内存是可以“重复使用的”。
这里不妨赘述一下,在同时运行的程序中(不管是否相同),变量的地址也可以是一样的,不妨自己验证一下。出现这种状况的原因是“虚拟内存”的命名问题,虽然虚拟内存的名称一样,但其物理地址是不相同的。
回到正题,那为什么同一程序的不同变量就能“共同”同一内存了呢?这里是因为auto变量的内存存储是可弹压的,存储在栈内存中。
因此是可以重复使用的。
这也是为什么出现了地址一样的原因。下面来看看一个函数(func)是怎么加载到内存的
结果如下:
仔细看会发现,形参b(后面一个)的地址比形参a(前面一个)优先分配内存。这是其他语言的编译器不存在的。明白了吧,这就是为什么printf这个函数可以有多个参数!因为C编译器中,形参的加载是从后往前的!
总之,内存可以分为:只读内存区,静态内存和栈内存。至于这么分配的,还是看上面吧。