c语言内存布局

在Linux系统下一个c语言进程的内存布局

Linux平台下的虚拟内存管理

写C程序时,我们经常会打印一个指针地址,说这个指针指向某某内存地址.可这些地址是真实物理内存地址吗?不是!这些只是虚拟内存地址.
当一个C程序调入内存开始执行后,在内存中就会产生一个进程.而在多任务操作系统中每个进程都拥有一片属于自己的内存空间(内存沙盘),这个沙盘就是虚拟地址空间,在32位下是一个4GB的大小的地址块,这些虚拟地址通过页表映射到物理内存.
但系统并不会真的一下分配给每一个进程4GB的物理内存空间的映射= =(不现实啊),这4GB只能是说逻辑地址,它会随着进程的真实需要自动扩展映射到物理内存空间,最大到4GB.
4GB(地址0-0xFFFFFFFF)其中1GB必须保留给系统内核(这是Linux平台下),也就是说进程自身只能拥有3GB的地址(0-0xC0000000),如图
cmd-markdown-logo
代码区:程序(函数)代码所在,由编译而得到的二进制代码被载入至此.代码区是只读的!有执行权限.代码区一般都从0x08048000地址开始(linux下).值得注意的是,字符串字面值(如”Hello World”)就存储在这个区.
数据段和BSS段:合称静态区(全局区),用来存储静态(全局)变量.区别是 前者(数据段)存储的是已初始化的静态(全局)变量,可读写.
后者(BSS段)存储的是未初始化的静态(全局)变量,可读写.
堆:自由存储区.不像全局变量和局部变量的生命周期被严格定义,堆区的内存分配和释放是由程序员所控制的.申请方式:C中是malloc函数,C++中是new标识符.
栈:由系统自动分配和释放.存储局部(自动)变量. 一般说的堆栈,其实是指 栈!
另外,值得注意的是,堆是由低地址向高地址分配空间;栈却是由高地址向低地址分配空间.
下面这段代码进一步说明C程序中各数据的内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
int i1 = 10; //静态全局区(data段)
int i2; //静态全局区(bss段)
static int i3 = 30; //静态全局区(data段)
const int i4 = 40; //代码区!!!
void fun(int i5) //栈区
{
int i6 = 60; //栈区
static int i7 = 70; //静态全局区(data段)
const int i8 = 80; //栈区!!!
char* str1 = "ABCDE"; //代码区(字符串常量)
char str2[] = "ABCDE"; //栈区(字符数组)
int* pi = malloc(sizeof(int)); //堆区
printf("&i5=%p\n", &i5);
printf("&i6=%p\n", &i6);
printf("&i7=%p\n", &i7);
printf("&i8=%p\n", &i8);
printf("str1=%p\n", str1);
printf("str2=%p\n", str2);
printf("pi=%p\n", pi);
free(pi);
}

int main(void)
{
printf("&i1=%p\n", &i1);
printf("&i2=%p\n", &i2);
printf("&i3=%p\n", &i3);
printf("&i4=%p\n", &i4);
fun(50);
return 0;
}

程序输出:
cmd-markdown-logo
至此,从地址大小比较可以看出
1)静态(static)全局变量 和 静态(static)局部变量 都在 静态全局区.
2)全局常量i4保存在代码区,而局部常量i8保存在栈区.
所以最上面的问题是,代码区只读,修改全局常量会引发运行时段错误,而局部常量是可以成功赋值修改的.
3)字符串字面值在代码区(所以不可修改),但是字符指针str1在栈区;字符数组str2在栈区(所以可以修改).

堆和栈的区别

  • 管理方式:对于栈来讲,是由编译器自动管理;对于堆来说,释放工作由程序员控制,容易产生 memory leak。
  • 空间大小:一般来讲在 32 位系统下,堆内存可以达到接近 4G 的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在 VC6 下面,默认的栈空间大小大约是 1M。
  • 碎片问题:对于堆来讲,频繁的new/delete 势必会造成内存空间的不连续,从而造成大量碎片,使程序效率降低;对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,永远都不可能有一个内存块从栈中间弹出。
  • 生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
  • 分配方式:堆都是动态分配的,没有静态分配的堆;栈有 2 种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配,动态分配由 alloca 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,不需要我们手工实现。
  • 分配效率:栈是机器系统提供的数据结构,计算机会在底层分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高; 堆则是 C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,然后进行返回。显然,堆的效率比栈要低得多。

无论是堆还是栈,都要防止越界现象的发生。

关于 Global 和 Static 类型的一点讨论

  1. static 全局变量与普通的全局变量有什么区别 ?
    全局变量(外部变量)的定义之前再冠以 static 就构成了静态的全局变量。
    全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。
    这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
    由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
    static 全局变量只初使化一次,防止在其他文件单元中被引用。
  2. static 局部变量和普通局部变量有什么区别 ?
    把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
    static 局部变量只被初始化一次,下一次依据上一次结果值。
  3. static 函数与普通函数有什么区别?static 函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.static 函数在内存中只有一份(.data),普通函数在每个被调用中维持一份拷贝。