欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C编译器内存分配

程序员文章站 2022-05-04 10:11:10
...

我们知道,可以用printf(“%p”,xx)来打印出变量的地址(虚拟地址),因此可以借此窥探变量在内存中的分配。

首先谈谈什么是虚拟地址,操作系统会给每个进程分配一个虚拟地址,这和C语言没有关系,而是操纵系统和CPU共同努力的结果。这样,就算程序产生了一个bug,破坏了内存区,也不会影响其他进程的操作。

下面是进入正题。下面的程序试图打印出全局变量,文件内静态变量,函数入口地址,字符串的地址,通过malloc分配的内存,函数内部静态变量和函数内部变量,

C编译器内存分配

结果是:

C编译器内存分配

可以看出函数内部的变量离其他变量比较远。

内存
函数入口地址;  字符串常量 0x56405 44da  xxx
全局变量,静态变量(归为静态变量) 0x56405 46db  xxx
用malloc分配的内存地址 0x56405 53c1 xxx

可以看出,表格中的三项离得比较近,但是却仍有距离。(毕竟16进制)

但相对于函数内部变量,也就是动态变量,即auto类型的变量,还是算近的。

再来看看区别:下面打出main函数里的auto类型变量和静态变量

C编译器内存分配

发现auto类型的变量彼此都离得很近。

而且,仔细看会发现,func2_var的地址竟然和func1_var的地址是相同的



那么先来说说:函数入口地址和字符串常量的地址内存吧

因为函数和字符串本身在运行程序的时候是不可能改写的,所以它被分配到内存的只读区域。 

虽然说很久以前函数是可以改写的,但会把程序弄得晦涩难懂,因此现代大多数操作系统都禁止了。

字符串常量就不用说了,常量嘛肯定不能修改。这也就是为什么当一个指针指向一个字符串时,其所指向的值是不可修改的。如

char *p = "I am a student";
*(p+2) = 'b';

这是错误的,在linux中会报出段内存错误。因为指针指向的是只读内存,只读内存不能修改。

但修改指针的值:p = &a,就肯定是正确的。这里不再解释。


下面来看看:静态变量

静态变量包括:静态局部变量、全局变量 和 文件内静态变量。

在大多数的书中都写到,静态变量在程序开始运行的时候就被加载到内存中了。

从上面的运行结果上看,静态变量们都离得很近。

我们知道,一个C源代码(当然可以是多个)要想编译成可执行文件,必须经过预处理、汇编、编译和链接,在编译的时候会生成一个.o的目标文件,多个目标文件链接生成一个可执行文件。

关键就在链接这一步,对于静态变量来说,在跨越不同目标文件时会被当成同一个对象对待。对局部变量则相反,即使不同源文件的局部变量用了相同的名称,在同一个程序中也会被区别对待。

于是对静态变量来说,为了方便将名称和静态变量结合起来,各目标(.o)文件都具备一个符号表,linux中可以使用nm来窥视他们。

C编译器内存分配

如图。可以看出静态变量,和函数入口地址全在里边。但局部变量呢?没有是吧。

连接器就根据这个符号表,给这些变量们分配内存。因此,静态变量在程序执行开始的时候就分配了内存,这句话是正确的。


最后在来看看 auto类型的变量

从运行结果上看func2_var的地址和func1_var的地址是相同的。这就表明auto变量的内存是可以“重复使用的”。

这里不妨赘述一下,在同时运行的程序中(不管是否相同),变量的地址也可以是一样的,不妨自己验证一下。出现这种状况的原因是“虚拟内存”的命名问题,虽然虚拟内存的名称一样,但其物理地址是不相同的。

回到正题,那为什么同一程序的不同变量就能“共同”同一内存了呢?这里是因为auto变量的内存存储是可弹压的,存储在栈内存中。

C编译器内存分配

因此是可以重复使用的。

这也是为什么出现了地址一样的原因。下面来看看一个函数(func)是怎么加载到内存的

C编译器内存分配

结果如下:

C编译器内存分配

仔细看会发现,形参b(后面一个)的地址比形参a(前面一个)优先分配内存。这是其他语言的编译器不存在的。明白了吧,这就是为什么printf这个函数可以有多个参数!因为C编译器中,形参的加载是从后往前的!


总之,内存可以分为:只读内存区,静态内存和栈内存。至于这么分配的,还是看上面吧。