c语言内存管理
a、作用域:在定义变量的{}之内有效b、声明周期:程序运行至变量定义处开辟内存空间,所在函数运行结束之后释放空间c、未初始化的变量值:随机。
1、局部变量&静态局部变量
1.1、局部变量
a、作用域:在定义变量的{}之内有效
b、声明周期:程序运行至变量定义处开辟内存空间,所在函数运行结束之后释放空间
c、未初始化的变量值:随机
1.2、静态局部变量
a、作用域:在定义变量的{}之内有效
b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间,这是因为
static修饰局部变量,是改变了变量的存储类型本来局部变量是放在栈区的,被static修饰后存放
内存的静态区了,因为存储类型的改变,从而导致生命周期变长。static修饰局部变量的时候改变,这个变量的生命周期。但是,不影响作用域
c、未初始化的变量值:0,这个不同于自动变量值是随机的
ps:自动变量(auto)外部变量(extern)静态变量(static)寄存器变量(register)
以下例子有个疑问,为什么函数func执行之后对应的指针p仍然可以打印出func的局部变量的内容:只能说变量已经超出了作用域,但是栈空间可能没有被复写


#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//1、局部变量
//a、作用域:在定义变量的{}之内有效
//b、声明周期:程序运行至变量定义处开辟内存空间,所在函数运行结束之后释放空间
//c、未初始化的变量值:随机
//2、静态局部变量
//a、作用域:在定义变量的{}之内有效
//b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间,这是因为
// static修饰局部变量,是改变了变量的存储类型本来局部变量是放在栈区的,被static修饰后存放
// 内存的静态区了,因为存储类型的改变,从而导致生命周期变长。static修饰局部变量的时候改变
// 这个变量的生命周期。但是,不影响作用域
//c、未初始化的变量值:0,这个不同于自动变量值是随机的
int* func() {
static int num = 10;
//int a = 1;
{
num++;
printf("func-num:%d\n",num);
}
//return &a;//err不安全,a的变量可能会被释放掉
return #//只要程序不死,都是可以使用的
}
void main(int argc, char* argv[]) {
int* p = NULL;
p = func();
printf("main-num:%d\n", *p);//如果func返回的&a则为 1 如果是return &num 11
p = func();
printf("main-num:%d\n", *p);//如果func返回的&a则为 1 如果是return &num 12
system("pause");
}

2、全局变量、
2.1、全局变量
a、作用域:整个工程所有文件,其他文件引用需要extern int num生命改全局变量在其他文件定义了
b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间
c、未初始化的变量值:0
2.2、静态全局变量
a、作用域:当前文件
b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间
c、未初始化的变量值:0
总结:
作用域:局部变量(普通局部和静态局部)在0范围之内,普通全局变量作用域在整个工程,静态全局作用当前文件。
生命周期:只有普通局部变量是运行至变量定义处时开辟,函教结束释放,其他变量都是执行man函数之前就已经开空间,程序结束之后才释放空间
初始化的值:只有普通局部未初始化的值为随机,其他为0
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//1、全局变量
//a、作用域:整个工程所有文件,其他文件引用需要extern int num声明该全局变量在其他文件定义了
//b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间
//c、未初始化的变量值:0
//2、静态全局变量
//a、作用域:当前文件
//b、声明周期:在执行main函数之前就已经开辟空间,程序结束之后才会释放空间
//c、未初始化的变量值:0
//总结:
//作用域:局部变量(普通局部和静态局部)在0范围之内,普通全局变量作用域在整个工程,静态全局作用当前文件。
//生命周期:只有普通局部变量是运行至变量定义处时开辟,函教结束释放,其他变量都是执行man函数之前就已经开空间,程序结束之后才释放空间
//初始化的值:只有普通局部未初始化的值为随机,其他为0
int num32;//全局变量,作用整个工程
//extern int num64;//引用变量num64在其他文件定义
static int num;//静态全局变量,只作用于本文件
int* func() {
int a = 1;
num32++;
//p = &a;
//return p;//不安全,a的变量可能会被释放掉
//return #
return &num32;
}
void main(int argc, char* argv[]) {
int* p = NULL;
p = func();
printf("main-num:%d\n", *p);//1
p = func();
printf("main-num:%d\n", *p);//2
system("pause");
}

3、全局函数&静态函数
静态函教就是在函数定义时加上static修饰的函数,静态函数只可以被当前文件函教调用
static void func()
普通的函数没有加任何修饰,就是全局函数,整个工程可以调用
例子
分析下图打印结果 7 18 20 7 *18 * 20

4、程序内存分布
text:区是代码区,就是程序编译之后源码的区域,在烧录之后一直位于Flash ROM中
data和bss都是指的全局变量以及函数内static的变量,区别是data是有初始值的而bss没有。data的初始值同样存在Rom里,当单片机启动的时候data的初始值会被加载到内存的相应位置,而bss则在启动的时候被置零。(这部分可以参考startup_stm32.s)
dec是text+data+bss的大小,hex就是dec转成16进制的值,并不是ROM占用哦,实际的比这个少。
所以最后bin文件的大小是text+data区域的大小,去看看STM32_PD.bin的大小,发现正好是9.32 KB (9,548 bytes),这些东西是要被写入单片机的ROM的。


5、内存操作函数
5.1函数memset
函数原型: void* __cdecl memset(void* _Dst,int _Val,int _Size);
作用:函数是内存赋值函数,用来给一块指定内存空间进行赋值的。
_Dst:为数组名,也可以是指向某一内存空间的指针
_v:为要填充的值
_Size:为要填充的字节数
返回值:为数组的首元素地址,可以用给数组清零
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//函数原型: void* __cdecl memset(void* _Dst,int _Val,int _Size);
// 作用:函数是内存赋值函数,用来给一块指定内存空间进行赋值的。
//_Dst:为数组名,也可以是指向某一内存空间的指针
//_v:为要填充的值
//_Size:为要填充的字节数
void main(int argc, char* argv[]) {
int a = 10;
//a = 0;// -> memset()
int* p = NULL;
p = memset(&a,0,sizeof(a));
printf("a:%d\n",*p);//a:0
char buf[10] = "";
strcpy(buf,"hello");
printf("buf:%s\n",buf);
//memset(buf,0,sizeof(buf));//buf:hello
//printf("buf:%s\n", buf);//buf:
memset(buf, 'a', sizeof(buf)-1);//buf:aaaaaaaaa 最后一个保存为0即字符\0
printf("buf:%s\n", buf);//buf:aaaaaaaaa
system("pause");
}

5.2 函数memcpy
声明:void* memcpy(void* str1, const void* str2, size_t n)
参数:
str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
n -- 要被复制的字节数。
返回值:该函数返回一个指向目标存储区 str1 的指针。
例子一
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//声明:void* memcpy(void* str1, const void* str2, size_t n)
//参数:
//str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
//str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
//n -- 要被复制的字节数。
//返回值:该函数返回一个指向目标存储区 str1 的指针。
void main(int argc, char* argv[]) {
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int b[10] = { 0 };
memcpy(b,a,sizeof(int)*5);//拷贝a的前五个元素到b中
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++) {
printf("i:%d ",b[i]);//i:1 i:2 i:3 i:4 i:5 i:0 i:0 i:0 i:0 i:0 请按任意键继续. . .
};
printf("\n");
memcpy(b, a+2, sizeof(int) * 5);//从a的第三个元素开始拷贝五个元素到b中
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++) {
printf("i:%d ", b[i]);//i:3 i:4 i:5 i:6 i:7 i:0 i:0 i:0 i:0 i:0 请按任意键继续. . .
};
system("pause");
}

例子二
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void main(int argc, char* argv[]) {
char str1[128] = "";
char str2[128] = "abc\0ea\0serwg";
char*p = memcpy(str1,str2,10*sizeof(char));
for (int i = 0; i < 10;i++) {
printf("%d ",str1[i]);
};//97 98 99 0 101 97 0 115 101 114
printf("\n");
strncpy(str1, str2, 10 * sizeof(char));
for (int i = 0; i < 10; i++) {
printf("%d ", str1[i]);
};//97 98 99 0 0 0 0 0 0 0
printf("\n");
system("pause");
}

5.3、函数memcmp
声明 int memcmp(const void *str1, const void *str2, size_t n)
描述
C 库函数 int memcmp(const void *str1, const void *str2, size_t n)) 把存储区 str1 和存储区 str2 的前 n 个字节进行比较。
参数
- str1 -- 指向内存块的指针。
- str2 -- 指向内存块的指针。
- n -- 要被比较的字节数。
返回值
- 如果返回值 < 0,则表示 str1 小于 str2。
- 如果返回值 > 0,则表示 str1 大于 str2。
- 如果返回值 = 0,则表示 str1 等于 str2。
总结:mem*函数是对内存自己进行操作的,str是对字符串进行操作的所以strncmp会遇\0而止二memcmp不会
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//
void main(int argc, char* argv[]) {
char num1[] = { 1,0,3,4,5,6 };
char num2[] = {1,0,3,6,5,6,7};
char str1[] = "abcd\0abcd";
char str2[] = "abcd\0bbcd";
printf("%d\n", memcmp(num1,num2,7*sizeof(char)));//-1//-1//基于内存字节比较的即使是\0依然操作
printf("%d\n", strncmp(num1, num2, 7*sizeof(char)));//0 遇到\0则停止
printf("%d\n", memcmp(str1, str2, sizeof(str1)));//-1//基于内存字节比较的即使是\0依然操作
printf("%d\n", strncmp(str1,str2,sizeof(str1)));//0 遇到\0则停止
printf("\n");
system("pause");
}

6、堆区申请内存
6.1、malloc
描述:C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
声明:void *malloc(size_t size)
参数:size -- 内存块的大小,以字节为单位。
返回值:该函数返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。
6.2、calloc
calloc函数也是与free()函数配套使用的,使用方式与malloc几乎相同,也是在堆区申请动态内存空间。头文件:stdlib.h,返回类型为空指针,size_t num为元素个数,size_t size为每个元素的字节大小。
calloc函数的原型:void* calloc(size_t num ,size_t size)
6.3、free
free(需要加头函数 #include<stdlib.h>)
释放动态内存空间
函数原型: void free(void *ptr);
free函数释放ptr参数指向的内存空间。该内存空间是由malloc、calloc或realloc函数申请的。否则,该函数将导致未定义行为,如果ptr参数是NULL,则不执行任何操作。
注意:该函数不会修改ptr参数的值,所以调用后它仍然指向原来的地方(边为非法空间)
ps:1、malloc与calloc的区别:参数单位的区别:malloc(单位:字节):malloc(10 * sizeof(int));或malloc(40)而calloc calloc(10 ,sizeof(int))
malloc的使用效率较高而calloc在返回在堆区申请的那块动态内存的起始地址之前,会将每个字节都初始化为0 2、都可以使用free(p)的方式释放堆区内存free(p); p = NULL;不可以多次free同意个内存,free的内存地址必须是malloc、calloc分配的返回指针,不可以是运算之后的指针比如free(p+1)是错误的
例子一
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//
void main(int argc, char* argv[]) {
//申请一个有十个元素的整形数组
int* p = (int*)malloc(sizeof(int)*10);
*p = 1000;
*(p + 5) = 3000;
for (int i = 0; i < 10;i++) {
printf("i:%d ",*(p+i));
}//i:1000 i:-842150451 i:-842150451 i:-842150451 i:-842150451 i:3000 i:-842150451 i:-842150451 i:-842150451 i:-842150451
printf("\n");
free(p);
for (int i = 0; i < 10; i++) {
printf("i:%d ", *(p + i));
}//i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307 i:-572662307
p = NULL;
system("pause");
}
例子二
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//
void main(int argc, char* argv[]) {
//申请一个又1024个元素的字符数组
char *p = calloc(1024,sizeof(char));
//memset(p,0,1024);//这里就不需要这一步操作了
strcpy(p,"helloworld!");
if (p == NULL) {
return;
}
for (int i = 0; i < 1024;i++) {
printf("%c ",*(p+i));
};
free(p);//free的时候一定是calloc/malloc返回的指针,不可以是运算之后的指针
//free(p);//不可以多次free
p = NULL;
system("pause");
}

6.4、函数返回值问题
看函数内部定义的变量是否能作为返回值,区域与变量是否被释放掉(思考有那些场景,局部变量全局变量的区别)
如果想形象源指针,实参传递指针,只拷贝不能影响源数据
例子一
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int* func() {
int a = 10; //栈区空间
return &a;
}
void main(int argc, char* argv[]) {
int* p = func();//p指向的是fuc的局部变量的内存地址,非法
*p = 20;//err
printf("p:%d\n",*p);
p = NULL;
system("pause");
}
例子二
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int* func() {
static int a = 10; //保存在data区空间,在程序整个生命进程中都可以用
return &a;
}
void main(int argc, char* argv[]) {
int* p = func();//p指向data区
*p = 20;//合法
printf("p:%d\n",*p);
p = NULL;
system("pause");
}
例子三
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int* func() {
char* p = malloc(1024);
return p;//正确 堆区地址如果没有free是可以返回使用的
}
void main(int argc, char* argv[]) {
char* p = func();//p指向堆区
p = "helloworld!";//err "helloworld!"是字面量存在字面量常量区,等于把 字面量常量区的地址重新赋值了p
strcpy(p,"helloworld!");//合法
printf("p:%d\n",*p);
p = NULL;
system("pause");
}

例子四
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void mem_p(char* p) {
p = malloc(1024);//实际程序指向结束该变量已经释放,导致main中的指针p仍然是NULL
return;
}
void main(int argc, char* argv[]) {
char* p = NULL;
mem_p(p);//实际传递的p到mem_p中发生的是值拷贝,mem_p函数执行的过程并不能影响到main中的p指针指向,可以通过多级指针比如mem_p(&p)来传递指针反过来影响main函数中p的指向,见例子六
strcpy(p,"hellowold!");
printf("p:%s",p);
system("pause");
}


例子五
通过传递值得方式无法无法修改原来指针的内容
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char * mem_p(char* p) {
p = malloc(1024);
return p;
}
void main(int argc, char* argv[]) {
char* p = NULL;
p = mem_p(p);
strcpy(p,"hellowold!");
printf("p:%s",p);
system("pause");
}

例子六
可以通过传递指针的方式修改变量的值
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void mem_p(char** p) {
*p = malloc(1024);//实际
return;
}
void main(int argc, char* argv[]) {
char* p = NULL;
mem_p(&p);
strcpy(p, "hellowold!");
printf("p:%s", p);
system("pause");
}


例子七
//#define _CRT_SECURE_NO_WARNING
//#include<stdio.h>
//#include<string.h>
//#include<stdlib.h>
//
//char * mem_p() {
//
// char *p = malloc(1024);//实际
// return p ;
//}
//void main(int argc, char* argv[]) {
//
// char* p = NULL;
// p = mem_p();
// strcpy(p, "hellowold!");
// printf("p:%s", p);
//
// system("pause");
//
//}
#define _CRT_SECURE_NO_WARNING
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char * mem_p() {
char *p = malloc(1024);//实际
return p ;
}
void main(int argc, char* argv[]) {
char *p = mem_p();
strcpy(p, "hellowold!");
printf("p:%s", p);
system("pause");
}

7、内存泄露
7.1、内存泄露
程序申请空间,但是没有释放动作,比如值malloc/calloc但是没有对应的free,导致内存一直申请没有释放
7.2内存污染
像没有申请的内存写入数据
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)