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

C语言程序里全局变量、局部变量、堆、栈的存储区域

程序员文章站 2024-01-28 17:14:58
...

一、存储区域介绍

  • C语言在内存中一共分为如下几个区域
区域 作用
内存栈区 存放局部变量名
内存堆区 存放new或者malloc出来的对象
常数区 存放局部变量或者全局变量的值
静态区 用于存放全局变量或者静态变量
代码区 二进制代码
  • 区域的解释

栈区(stack)–由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区(heap)–般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。

全局区(静态区)(static)–全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(RW),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(ZI)。程序结束后有系统释放。 和“栈”一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。

文字常量区–常量字符串就是放在这里的。程序结束后由系统释放 (RO)

程序代码区–存放函数体的二进制代码。 (RO)

常量存储区—和“全局/静态存储区”一样,通常是用于那些在编译期间就能确定存储大小的常量的存储区,并且在程序运行期间,存储区内的常量是全局可见的。这是一块比较特殊的存储去,他们里面存放的是常量,不允许被修改。

  • 内存存在两种属性:静态分配内存和动态分配内存。

静态分配内存:是在程序编译和链接时就确定好的内存。
动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存。

  • 堆栈

堆和栈都是动态分配内存,两者空间大小都是可变的。

Stack: 栈,存放Automatic Variables,按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但*性差,最大空间不大。

通常是用于那些在编译期间就能确定存储大小的变量的存储区,用于在函数作用域内创建,在离开作用域后自动销毁的变量的存储区。通常是局部变量,函数参数等的存储区。他的存储空间是连续的,两个紧密挨着定义的局部变量,他们的存储空间也是紧挨着的。栈的大小是有限的,通常Visual C++编译器的默认栈的大小为1MB,所以不要定义int a[1000000]这样的超大数组。

Heap: 堆,*申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但*性大,可用空间大。
通常是用于那些在编译期间不能确定存储大小的变量的存储区,它的存储空间是不连续的,一般由malloc(或new)函数来分配内存块,并且需要用free(delete)函数释放内存。如果程序员没有释放掉,那么就会出现常说的内存泄漏问题。需要注意的是,两个紧挨着定义的指针变量,所指向的malloc出来的两块内存并不一定的是紧挨着的,所以会产生内存碎片。另外需要注意的一点是,堆的大小几乎不受限制,理论上每个程序最大可达4GB。
每个线程都会有自己的栈,但是堆空间是共用的。

  • Text & Data & Bss

.text: 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)。 这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个text段。

.data: 也有的地方叫GVAR(global value),用来存放程序中已经初始化的非零全局变量。静态分配。 data又可分为读写(RW)区域和只读(RO)区域。
-> RO段保存常量所以也被称为.constdata
-> RW段则是普通非常全局变量,静态变量就在其中 .bss: 存放程序中为初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。

  • 从低地址到高地址
    C语言程序里全局变量、局部变量、堆、栈的存储区域

二、在ubuntu系统中输出信息进行验证

  • 输入代码main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
void before()
{
 
}
 
char g_buf[16];
char g_buf2[16];
char g_buf3[16];
char g_buf4[16];
char g_i_buf[]="123";
char g_i_buf2[]="123";
char g_i_buf3[]="123";
 
void after()
{
 
}
 
int main(int argc, char **argv)
{
        char l_buf[16];
        char l_buf2[16];
        char l_buf3[16];
        static char s_buf[16];
        static char s_buf2[16];
        static char s_buf3[16];
        char *p_buf;
        char *p_buf2;
        char *p_buf3;
 
        p_buf = (char *)malloc(sizeof(char) * 16);
        p_buf2 = (char *)malloc(sizeof(char) * 16);
        p_buf3 = (char *)malloc(sizeof(char) * 16);
 
        printf("g_buf: 0x%x\n", g_buf);
        printf("g_buf2: 0x%x\n", g_buf2);
        printf("g_buf3: 0x%x\n", g_buf3);
        printf("g_buf4: 0x%x\n", g_buf4);
 
        printf("g_i_buf: 0x%x\n", g_i_buf);
        printf("g_i_buf2: 0x%x\n", g_i_buf2);
        printf("g_i_buf3: 0x%x\n", g_i_buf3);
 
        printf("l_buf: 0x%x\n", l_buf);
        printf("l_buf2: 0x%x\n", l_buf2);
        printf("l_buf3: 0x%x\n", l_buf3);
 
        printf("s_buf: 0x%x\n", s_buf);
        printf("s_buf2: 0x%x\n", s_buf2);
        printf("s_buf3: 0x%x\n", s_buf3);
 
        printf("p_buf: 0x%x\n", p_buf);
        printf("p_buf2: 0x%x\n", p_buf2);
        printf("p_buf3: 0x%x\n", p_buf3);
 
        printf("before: 0x%x\n", before);
        printf("after: 0x%x\n", after);
        printf("main: 0x%x\n", main);
 
        if (argc > 1)
        {
                strcpy(l_buf, argv[1]);
        }
        return 0;
}

l_buf/l_buf2/l_buf3 ,直接定义,是由编译器自动分配的,存储在栈中
s_buf/s_buf2/s_buf3,定义static,编译器编译时分配内存。为全局变量,在全局初始化区
p_buf/p_buf2/p_buf3,他们指向的空间,通过malloc申请空间,存放在堆中
g_buf/g_buf2/g_buf3/g_buf4/,定义的为全局变量,在全局初始化区

  • 运行结果
    C语言程序里全局变量、局部变量、堆、栈的存储区域
  • 分析结果
    栈存放区域是由高地址到低地址向下增长
    堆存放区是由低地址到高地址像上增长
    静态变量地址从高地址到低地址向下增长
    函数地址是从低地址到高地址向上增长

三、在Keil中针对stm32系统进行验证

  • 总结
内存高地址 栈区
堆区
.bss段
.data段
常量区
内存低地址 代码区
  • 在MDK中编译后可以看到Code、RO-data、RW-data、ZI-data的大小

Code是存储程序代码的;
RO-data是存储const常量和指令
RW-data是存储初始化值不为0的全局变量
ZI-data是存储未初始化的全局变量或初始化值为0的全局变量

C语言程序里全局变量、局部变量、堆、栈的存储区域

  • keil中的代码
#include "delay.h"
#include "usart.h"
#include "string.h"
#include <stdio.h>
#include <stdlib.h>

int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main(void)
 {
	delay_init();	    	 //延时函数初始化	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
	uart_init(115200);	 //串口初始化为115200
	 
	 while(1)
	{
    static int m1=2, m2;
    int i = 1;
    char *p;
    char str[10] = "hello";
    char *var1 = "123456";
    char *var2 = "abcdef";
    int *p1=malloc(4);
    int *p2=malloc(4);
    free(p1);
    free(p2);
    printf("栈区-变量地址");
	printf("   i:%p\r\n", &i);
	printf("                p:%p\r\n", &p);
	printf("              str:%p\r\n", str);
    printf("堆区-动态申请地址");
    printf("  %p\r\n", p1);
    printf("                   %p\r\n", p2);
    printf("\r\n.bss段\r\n");
	printf("全局外部无初值 k2:%p\r\n", &k2);
	printf("静态外部无初值 k4:%p\r\n", &k4);
	printf("静态内部无初值 m2:%p\r\n", &m2);
    printf("\r\n.data段\r\n");
    printf("全局外部有初值 k1:%p\r\n", &k1);
    printf("静态外部有初值 k3:%p\r\n", &k3);
    printf("静态内部有初值 m1:%p\r\n", &m1);
    printf("\r\n常量区\r\n");
    printf("文字常量地址     :%p\r\n",var1);
    printf("文字常量地址     :%p\r\n",var2);
    printf("\r\n代码区\r\n");
    printf("程序区地址       :%p\r\n",&main);
	}
}
  • 串口调试结果
    C语言程序里全局变量、局部变量、堆、栈的存储区域
  • 分析结果可知,stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的。

四、总结

通过这次学习让我对C程序的内存分配有进一步的认识,知道一个C程序内存包括的部分,了解了栈和堆地址变化的不同。而且在不同系统中,区域内的地址值变化不一定是相同的。

五、参考资料

1、https://blog.csdn.net/feier7501/article/details/8564300
2、https://blog.csdn.net/jirryzhang/article/details/79518408
3、https://blog.csdn.net/qq_43279579/article/details/110308101