在编写程序时,内存的控制是很重要的一部分。关于全局变量和局部变量,静态变量的关系如何?以及他们在内存在是存储于哪部分的?做个记录,以便往后查看。
1. 全局变量和静态变量
全局变量:又称外部变量,与之相对的是局部变量,是从变量的作用域上来考量的。全局变量定义与函数外面,作用于整个程序;而局部变量则是定义在函数里面,仅作用于函数内部,无法跨函数作用。全局变量为静态分配,即程序执行之前就进行内存分配,不再改变(内存不再改变);而局部变量则是动态分配,在程序执行的时候进行内存分配。
静态变量:是从变量的内存分配上来考量的,与之相对的是动态变量。静态变量如其名,采用静态分配方式的变量,表现在代码程层面就是用 static 或 extern 关键字修饰的变量即为静态变量。静态变量分为static和extern,两者作用域不同,static修饰的只能作用于单个程序文件,而extern 则可以作用于整个程序(多文件)。
全局变量和静态全局变量:当程序只有一个文件构成时,两个没有什么区别;当多个文件存在时,static全局变量则使得该变量为源文件独享(只作用于单个文件),而 extern 全局变量则可以作用整个程序,包括其他源文件。
那么静态局部变量呢?静态局部变量作用域与局部变量一致,只不过生命周期不一样,内存存储地点也不一样。静态局部变量是程序执行之前静态分配内存,程序执行结束之后结束。
看到有人提到静态变量无法改变,这是错误的。静态变量只是内存分配为静态分配方式,内存不变,并不是说不可以改变,静态变量也是可以更改存储内容的。
2. 全局变量,静态变量在内存中存放位置
我们知道一个汇编程序分为:代码段,数据段,堆栈段(这里的堆栈就是指的栈,stack)。从内存上说,数据段又可分为:静态存储区,堆(heap)和BSS(Block Started by Symbol)区。
代码段:就是存放程序二进制代码区域
静态存储区(.data):用于存放已初始化的全局变量和静态变量
堆(heap):数据区,用于动态分配的存储。一般由程序员控制分配和释放,malloc 和 new 的数据存放于堆中
未初始化全局(静态)变量BSS:未初始化的全局变量和静态变量,全局变量和静态变量默认初始化为0
堆栈(stack):通常理解就是局部变量,函数调用等所需存储
为了了解全局变量和静态变量等内存存储位置,talk is cheap, show the code! 给出一个测试案例,然后用objdump 或者 readelf反汇编指令,进行查看该程序生成的对应汇编程序结构,获取各个变量的内存段分配。
-
// tets.c
-
#include <stdio.h>
-
static int var_global_static_a = 1; // 初始化静态全局变量
-
static int var_global_static_b; // 未初始化静态全局变量
-
int var_global_a = 1; // 初始化全局变量
-
int var_global_b; // 未初始化全局变量
-
int main()
-
{
-
static int var_static_a = 1; // 初始化静态局部变量
-
static int var_static_b; // 未初始化静态局部变量
-
int var_a = 1; // 初始化局部变量
-
int var_b; // 未初始化局部变量
-
return 0;
-
}
结果如下图所示:
objdump -t test.o
执行结果可知:已初始化的全局变量和静态变量(无论全局还是局部)都是存放于.data 段,而未初始化的静态变量则是存放在.bss 段,未初始化的全局变量存放在 .comment,这个不知道是干嘛的,但其实也是应该是 .bss 类似。而局部变量则并未出现在这里,因为局部变量位于堆栈段,是需要程序执行时进行分配的,所以这个只进行了编译的obj文件中并没有这两个变量的内存分配。
.data | var_global_static_a | 初始化全局变量和静态变量 |
var_static_a | ||
var_global_a | ||
.bss | var_global_static_b | 未初始化全局变量和静态变量 |
var_static_b | ||
var_global_b (显示是 .COM) | ||
未分配 | var_a | 局部变量 |
var_b |
如果全局变量初始化都为 0 呢?结果如下所示,全部都变成了 .bss 段。其实0是全局变量和静态变量的默认初始化值,即如果没有主动初始化而直接使用,则是默认初始化为0 。所以当主动初始化为0时,还是当成没有初始化处理,存放于 .bss 段
3. 堆(heap)和堆栈段(stack)
堆和栈总是一起别人提起,所以这里也放在一起来讲。有个概念需要区分开的,就是堆栈指的就是内存的stack部分,而不是堆和栈,堆是heap。有时候定义数组,因为过大,直接定义成局部变量的话就会爆,但是用 malloc 或者将数组变为全局变量,就不会爆。我们知道这这分别对应着堆栈存储 和 数据段(堆 和 静态存储区),那么他们的大小分别是多少呢?
3.1 堆的大小和堆溢出
堆作为数据段的一部分,理论上除去堆栈大小,代码段大小和全局变量静态变量等部分,剩下的都可以作为堆的内存分配范围。所以堆的大小一般比较大,几乎接近计算机内存(+虚拟内存)大小。关于堆溢出,如果持续进行 malloc 而不释放的话,那么就会产生堆溢出,爆堆。
3.2 堆栈大小和栈溢出
关于堆栈的大小,Linux下有个指令可以查看,一般情况下为8-10M。当局部变量大小大于这个数值或者递归层数过多时,进行发生栈溢出。
-
ulimit -a
-
# ulimit -s //可直接查看堆栈大小
这里显示栈大小8M,可以用程序测试一下,栈的大小到底是否是这么大。测试程序参考 linux下栈空间大小(ulimit):
-
#include <stdio.h>
-
#include <stdlib.h>
-
void test(int i)
-
{
-
char temp[1024*1024] = {0};
-
printf("%s %d num = %d!\r\n", __FUNCTION__, __LINE__, ++i);
-
test(i);
-
}
-
int main(void)
-
{
-
test(0);
-
return 0;
-
}
测试结果确并未如我上面参考的那个博主的一样,他得出Linux下主线程栈大小比子线程多大概2M的样子,可是我的测试结果都是一样(至少在M级是一样),结果如下:
结果证实了此前查询得出的栈大小为8M 这个事实(因为还有参数传递,和局部变量,所以是肯定到达不了8的)。为了知道我计算机的实际栈大小,以及后续的证实,进行了手动二分来确定栈具体大小(有点傻。。)
理论上堆栈空间8M=8*1024*1024 = 2*1024*1024 = 2097152 个int