c语言内存管理(待完善)
内容概要
一、动态内存管理
二、c语言内存布局
三、思考题
1、动态内存管理
c语言中的变量是要事先定义好才能使用,在程序执行过程中动态创建是不允许的。但是在C99后已经实现了变量动态创建
如果想要动态创建变量,可以使用库stdlib.h下提供的功能
-malloc(参数一)
参数一:要开辟多少字节的内存
功能:创建一个动态的内存,这个内存位于堆中
返回值:返回一个指向这个内存位置的void类型指针
#include <stdio.h> #include <stdlib.h> int main(void){ int *pa; pa = (int *)malloc(sizeof(int)); //创建一个int类型大小的内存空间 *pa = 120; printf("%d %p\n", *pa, pa); free(pa); //释放pa指向的内存,这个内存应该有malloc,calloc或者realloc开辟的内存(存放在堆中) return 0; }
需要注意的是malloc开辟的空间存放在堆区,这部分内存不会在函数调用结束之后释放,需要程序员指定才会释放
像是在python使用open()函数之后使用close()向操作系统发出回收文件资源的命令
#include <stdio.h> #include <stdlib.h> int main(){ int *pa; while (1){ pa = (int *)malloc(sizeof(int)); } return 0; }
把内存搞炸
#include <stdio.h> #include <stdlib.h> int main(){ int *pa; while (1){ pa = (int *)malloc(sizeof(int)); free(pa); } return 0; }
炸不了了
使用malloc也可能调用失败,只是函数会返回NULL指针
像操作数组那样操作返回的指针
#include <stdio.h> #include <stdlib.h> int main(){ int n; int *pa; // 动态创建 printf("please input how long you want:"); scanf("%d",&n); pa = (int *)malloc(n * sizeof(int)); // 像数组一样遍历,发现是随机值 for (int i = 0; i < n; i++){ printf("%d\n",pa[i]); } // 回收资源 free(pa); return 0; }
为"数组"初始化,利用memset函数
要使用memset函数,需要导入库string.h
-memset(参数一,参数二,参数三)
参数一:动态内存指针
参数二:初始化值
参数三:动态内存大小
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ int *pa; pa = (int *)malloc(8,sizeof(int)); // 初始化 memset(pa, 0 ,8 * sizeof(int)); for (int i = 0; i < 8; i++){ printf("%d\n",pa[i]); } return 0; }
切记要使用指针关联动态开辟的内存
#include <stdio.h> #include <stdlib.h> int main(){ int *pa; int num = 10; pa = (int *)malloc(sizeof(int)); pa = # // 再也找不到指向那个位置的内存地址了 return 0; }
同时不要对局部变量使用free
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ int *pa; int num = 10; pa = (int *)calloc(8,sizeof(int)); pa = # free(pa) } return 0; }
-calloc(参数一,参数二)
参数一:存放元素的个数
参数二:每个元素的长度
功能:在上面的例子中,会发现遍历未初始化的动态内存,会打印随机的数值。使用calloc函数可以在动态创建内存空间时设置初始值。
返回值:void类型指针
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ int *pa; pa = (int *)calloc(8,sizeof(int));for (int i = 0; i < 8; i++){ printf("%d\n",pa[i]); } return 0; }
得学会看源码了,哪怕只是皮毛
-memcpy拷贝内存空间
-memove拷贝内存空间
-memcmp比较内存空间
-memchr在内存空间中查找一个字符
-realloc(参数一,参数二)
参数一:要更新的动态内存地址
参数二:新的内存大小
功能:更新动态内存的大小
返回值:void类型的内存地址(这个void地址可能和传入的参数相同,也可能不同)
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ int *pa; pa = (int *)calloc(8,sizeof(int)); printf("before: %p\n",pa); pa = (int *)realloc(pa,16 * sizeof(int)); printf("after: %p\n",pa); //两次地址应该一样 free(pa); return 0; }
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(){ int *pa; pa = (int *)calloc(8,sizeof(int)); printf("before: %p\n",pa); pa = (int *)realloc(pa,1000 * sizeof(int)); printf("after: %p\n",pa); //两次地址应该不一样 free(pa); return 0; }
如果向realloc函数传入NULL指针,就相当于调用malloc函数
一个根据用户输入创建动态"整形数组"的小练习
#include <stdio.h> #include <stdlib.h> int main(){ int num; int count = 0; int *pnum = NULL; while (1){ printf("please input a number(input -1 end process):"); scanf("%d",num); if (num == -1){ for (int i = 0; i<count; i++){ printf("%d\n",pnum[i]); } break; } count++; pnum = (int *)realloc(pnum,count * sizeof(int)); pnum[count] = num; } }
2、c语言内存管理
c语言不同类型变量内存地址的比较
#include <stdio.h> #include <stdlib.h> int global_var_uninit; int global_var_init = 1024; void func(void); void func(void){ ; } int main(void){ int local_var_uninit; int local_var_init = 111; static int static_var_uninit; static int static_var_init = 512; char *str1_before = "You are right!"; char *str2_after = "hello world"; int *pa = (int *)malloc(sizeof(int)); printf("addr of local_var_uninit is: %p\n", &local_var_uninit); printf("addr of local_var_init is: %p\n", &local_var_init); printf("addr of pa is: %p\n", pa); printf("addr of static_var_uninit is: %p\n", &static_var_uninit); printf("addr of global_var_uninit is: %p\n", &global_var_uninit); printf("addr of static_var_init is: %p\n", &static_var_init); printf("addr of global_var_init is: %p\n", &global_var_init); printf("addr of str2_after is: %p\n", str2_after); printf("addr of str1_before is: %p\n", str1_before); printf("addr of func is: %p\n", func); return 0; }
内存分段图解
补充:栈的大小是固定的,一般用于存放函数执行时的形参变量和函数体内定义的变量,一般大小只有几M
使用size命令查看每个分段大小
在ubuntu终端中输入size a.out text data bss dec hex filename 2307 624 16 2947 b83 a.out
堆和栈区别
这里所涉及得堆和栈并不是数据结构和算法中的堆和栈
-堆:动态内存空间,在程序执行过程中动态创建的一些数据存放在这里,需要手动回收申请的空间
在堆中创建的同类数据是不连续的,一定程度上会缩小能够存储的数据的数量,但同时
为空间扩展带来方便
-栈:用于存放函数执行时的数据,在函数调用结束之后会自动销毁
在栈中创建的同类数据是连续的
#include <stdio.h> #include <stdlib.h> int main(){ int *pa = NULL; int *pb = NULL; printf("%d\n",sizeof(int *)); pa = (int *)malloc(sizeof(int)); pb = (int *)malloc(sizeof(int)); printf("pa:%p pb:%p\n", pa, pb); printf("&pa:%p &pb:%p\n", &pa, &pb); free(pa); free(pb); return 0; }
#include <stdio.h> #include <stdlib.h> int main(){ int *pa,*pb; printf("%d\n",sizeof(int *)); pa = (int *)malloc(sizeof(int)); pb = (int *)malloc(sizeof(int)); printf("pa:%p pb:%p\n", pa, pb); printf("&pa:%p &pb:%p\n", &pa, &pb); free(pa); free(pb); return 0; }
堆中两个数据相隔32个字节,但我们申请的是4个字节
栈中两个数据相隔8个字节,这正好是一个指针的长度
**待解决**在虚拟机上无论是堆还是栈,地址大小都是向上发展的,可能我理解还是有问题
3、思考题
#include <stdio.h> int main(void){ char a=0, b=0; int *pa = (int *)&b; *pa = 258; printf("%d %d\n", a, b); return 0; }
ubuntu虚拟机打印的结果为什么为1,2(在windows dev-c++上为0,2。应该不用管)
解析
函数体代码被保存在代码段中,调用函数时,将函数参数复制到栈中
char a ,b在栈中是连续的,char类型站据1个字节
将b的地址(类型为字符指针)强转为整形指针,pa解引用时会读取4个字节而非1个字节
*pa解引用赋值,258的二进制值为 100000010 共9个比特位
其中后8个比特位00000010存放于b对应的内存中
前一个比特位1存放于a对应的内存
打印时a的值就为1,而b的值就为2
验证
如果*pa = 528;
528的二进制值为1000010000
分开后为10和00010000
打印的值应为 a = 2, b = 16;
***待完善***
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结