C 内存管理(代码区、数据区、堆区、栈区)

1、内存区域的划分

        在c语言中,定义了4个内存区间:

代码区:.text段:存放二进制代码;

               .rodata段:存放常量。

                代码区的大小在程序运行前就已经确定,该内存区域一般是只读的

数据区:.data段:  存放已初始化的全局变量和静态变量;

              .bss段:存放未初始化的全局变量和静态变量;

                这一块的内存在程序编译时就已经分配好,在程序整个运行期间都存在。属于静态内存分配

堆区(heap):用于存放在程序运行时被动态分配的内存段。堆的大小不固定,可以动态增加和减少。使用malloc()等函数动态分配内存到堆上,使用free()等函数释放对应的动态分配内存。堆的最大容量受限于系统中有效的虚拟内存。

栈区(stack):用于存放程序运行时临时创建的局部变量,除此以外,被调用函数地址和其参数也会被压入发起调用的进程栈中,调用结束后,函数的返回值也会存放到栈中。

 扩展:虚拟内存:运行在支持mmu的cpu架构的操作系统中,如windows和linux,这类C程序中用到的逻辑地址并不是真实的内存地址(和单片机直接操作硬件寄存器是不同的),只是虚拟内存地址。在32位操作系统中,虚拟内存是一个大小4GB的逻辑上连续虚拟内存空间,其中0~3GB是用户空间,3GB到4GB是内核空间。

虚拟内存分为一页页(一般为4K一页)。程序启动时并不需要把所有内容载入到真实的内存中,操作系统和硬件会将虚拟内存和真实内存建立映射。当程序运行时,访问的虚拟内存如果不在真实内存中,就会触发缺页中断,将需要用到的页载入到真实内存中,然后再重新访问。假如真实内存满了,就将太久没使用的页转出到磁盘中。因此,程序就认为自己独立拥有了4G虚拟地址空间,就算真实内存不足4G,也可以运行4G的程序。

2、堆区和栈区的区别

 1、申请方式:栈的空间由操作系统自动分配和释放,堆上的空间需要程序员使用malloc\free手动分配和释放。如果不释放会造成内存泄漏。

2、申请大小限制和效率:栈的空间时有限的,在linux中,使用 ulimit -s 指令 ,可以看到看到栈的容量为8M。栈区以先进后出的方式自动分配时连续的内存单元,效率高。

 堆的大小受限与系统中有效的虚拟内存大小,系统是用链表来存储空闲的内存块,是不连续的。因此,堆的空间分配比较灵活,但容易产生内存碎片,相对来讲对效率低。

扩展:栈在C语言中的作用

1、用来存储临时变量。包括函数参数和函数内部定义的临时变量。函数调用中和函数调用相关的函数返回地址,函数中的临时变量,寄存器等均保存在栈中,函数调用返回后从栈中恢复寄存器和临时变量等函数运行场景。

        简单的说就是局部变量、函数地址和相关参数。当主函数要调用一个函数的时候,要对当前断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,然后是调用函数的参数,一般情况下参数是按照从右向左的顺序入栈,之后是调用函数的局部变量,注意静态变量是存放在全局内存区,是不入栈的;出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。

2、栈是多线程编程的基础,每一个线程都最少有一个自己专属的栈,用来存储本线程运行时各个函数的临时变量,维系函数调用和函数返回时的的函数调用关系和函数运行场景。

操作系统最基本的功能就是支持多线程编程,支持中断和异常处理,每个线程都有专属的栈,中断和异常处理也具有专属的栈。         

3、程序的组成

一个程序本质上都是由.text段、.data段、.bss段三个组成的。

text段和data段都在可执行文件中(嵌入式系统一般时固化在镜像文件中),由系统从可执行文件加载。

bss段不在可执行文件中,由系统初始化

#include <stdio.h>
int arr[100000];
int main(void)
{
	printf("hello,world!");
	return 0;
}

#include <stdio.h>
int arr[10000]={0,1,2,3,4,5,6,7,8,9};
int main(void)
{
	printf("hello,world!\n");
	return 0;
}

 程序2编译之后得到的可执行文件比程序1大很多。

所以,.bss是不占用可执行文件空间的,其内容由操作系统初始化;

.data需要占用,其内容由程序初始化。

.bss并不给该段的数据分配空间,只是记录数据所需空间的大小;

.data需要给数据分配空间,数据保存在目标文件中。

4、几种存储类型在内存空间中的分配

auto存储类型:对于局部变量,auto是默认的存储类型,不需要显示的指定。auto标识的变量存储在栈区中。

extern存储类型:extern用来声明在当前文件中引用当前项目中的其它文件中定义的全局变量。如果全局变量未被初始化,那么将被保存bss段中,更准确的说时记录在bss段,且在编译时,自动将其值赋值为0,如果已经被初始化,那么就被存在data段中。

register存储类型:register只是建议编译器将变量存储在寄存器中,声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。所以,register 变量 存储在寄存器中或者栈中。

static存储类型:被声明为静态类型的变量,无论是全局的还是局部的,都存储在数据区中,其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件。静态变量如果没有被初始化,则自动初始化为0。静态变量只能够初始化一次。

字符串常量:字符串常量存储在数据区中,其生存期为整个程序运行时间,但作用域为当前文件。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
 
int a = 0;//初始化的全局变量:保存在数据段
char *ptr1;//未初始化的全局变量:保存在BSS段
int main()
{
    int b;//未初始化的局部变量:保存在栈上
    char str[] = "abc";//"abc"为字符串常量保存在常量区;数组保存在栈上,
    //并将常量区的"abc\0"复制到该数组中。这个数组可以随意修改而不会有任何隐患,
    //而"123"这个字符串依然会保留在静态区中。
 
    char *ptr2;//p2保存在栈上
    char *ptr3 = "123456";//ptr3保存在栈上,"123456\0"作为字符串常量保存在常量区
    //注意:如果令ptr3[1] = 9; 则程序崩溃,指针可以访问但不允许改变常量区的内容
    //声明了一个指针ptr3并指向"123456\0"在常量区中的地址,事实上,ptr3应该声明为
    //char const *,以免可以通过p3[i]='\n'这一类的语法去修改这个字符串的内容。
    static int c = 0;//初始化的静态局部变量:保存在数据区的.data段
    
    ptr1 = (char *)malloc(sizeof(char) * 10);//分配的10字节区域保存在堆上
    ptr2 = (char *)malloc(sizeof(char) * 10);//分配的10字节区域保存在堆上

    strcpy(ptr1,"123456");
    //"123456\0"放在常量区,编译器可能会将它与p3所指向"123456"优化为一个地方
    
    return 0;  
}

eg:注意 区分char str[] = "123456" 和char *str = "123456" 的区别 
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 

char *str;
int main()
{
	char str1[]="abcdef";
	for(int i = 0;str1[i]!= '\0'; ++i){
		printf("%c",str1[i]);
		str1[i] += 6;
	}
	printf("\n");
	for(int i = 0;str1[i]!= '\0'; ++i){
		printf("%c",str1[i]);
	}
	printf("\n");
    //可以看到str1[]的内容是可以修改的
	char *str2="abcdef"; 
	char *str3="abcdef";
	printf("str1:%p,str2:%p,str3:%p\n",str1,str2,str3);
    //str2和str3所保存的地址是相同的,
    //是常量区的"abcdef\0"的地址,无法通过*(str2+i)的方式区修改str2指向的内容,
    //但str1的地址不是指向常量区的"abcdef\0",而是常量区"abcdef\0"会在str1的位置
    //会有一份相同内容的拷贝

	char *str4 =malloc(sizeof(char) * 10);
	printf("str4:%p\n",str4);
	strcpy(str4,"abcedf");
	printf("str4:%p\n",str4);
	
	strcpy(str,"abcedf");
	//没搞懂这里为什么会段错误,上一个程序运行就没问题
	return 0;
}

 

  • 5
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值