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 &num;//只要程序不死,都是可以使用的

}
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 &num;
	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内存污染

像没有申请的内存写入数据

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐