完全自学C(干货) —— 动态内存管理

目录

一,动态内存函数

malloc

free

calloc

realloc

二,常见的动态内存错误

经典试题

三,C/C++程序的内存开辟

栈区stack

堆区heap

数据段(静态区)static

代码段

 四,柔性数组 — flexible array

案例


  • 通常开辟的内存是固定的(数组的内存是编译时分配的,但申明时需指定长度);
  • 针对已事先指定内存大小比较合适,对事先不确定的内存大小可使用动态内存函数;

一,动态内存函数

  • 动态内存函数在堆区开辟空间;

malloc

 void* malloc( size_t size )

  • 在内存申请一块连续的空间,并返回指向这块空间的指针;
  • 如开辟成功,则返回一个指向开辟好空间的指针;
  • 如开辟失败,则返回一个NULL,因此返回值一定要做检查;
  • 返回值类型为void*,使用时需自行决定;
  • 如参数为0,malloc行为未定义,取决于编译器;
	//在堆区,开辟4个整型16个字节的空间
	int* p = (int*)malloc(4 * sizeof(int));

free

void free( void *memblock )

  • 专门用来做动态内存的释放和回收的;
  • 如参数指针指向的空间不是动态内存开辟的,那free行为是未定义的;
  • 如参数指针为NULL,则函数不做任何操作;
  • 释放后的指针将变为野指针,最好在设置为NULL,避免非法访问;

注:动态内存释放有两种方式

  • free主动释放;
  • 程序结束释放;
int main()
{
	//在堆区,开辟4个整型16个字节的空间
	int* p = (int*)malloc(4 * sizeof(int));
	//检查p是否为空指针
	if (p == NULL) 
	{
		perror("main"); //p为空指针返回错误信息
		return 0;
	}
	//释放动态内存空间
	free(p);
	p = NULL; //避免错误使用
	return 1;
}

calloc

void *calloc( size_t num, size_t size )

  • 开辟指定元素个数的内存,元素初始值为0;
  • num为元素个数,size为每个元素的大小;
  • 与malloc的区别,只在于每个字节初始化值为0;
int main()
{
	//在堆区,开辟4个整型16个字节的空间
	int* p = (int*)calloc(4, sizeof(int));
	if (p == NULL) 
	{
		perror("main"); 
		return 0;
	}
    printf("%d", *p); //0
	free(p);
	p = NULL; 
	return 1;	
}

realloc

void *realloc( void *memblock, size_t size )

  • 调整动态内存空间的大小,让内存管理更加灵活;
  • 如指向的空间之后有足够的内存空间,则直接追加;
  • 如指向的空间之后没有足够的内存空间,则重新开辟新的内存空间,并将原来内存数据拷贝过来且释放其空间,最后返回新的空间地址;
  • 用一个新的变量来接收realloc函数的返回值;
  • 如对空指针进行操作,等同于malloc;
int main()
{
	//在堆区,开辟4个整型16个字节的空间
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL) 
	{
		perror("main"); 
		return 0;
	}
	//将p的空间拓展为10整型40个字节的空间
	int* ptr = realloc(p, 10 * sizeof(int));
    if (ptr != NULL) 
	{
		p = ptr;
	}

	free(p);
	p = NULL; 
	return 1;
}
//二者等同
int* p = (int*)malloc(4 * sizeof(int));
int* p = (int*)realloc(NULL, 4 * sizeof(int));

完全自学C(干货) —— 动态内存管理_第1张图片

二,常见的动态内存错误

  • 动态内存的指针可能为NULL,在进行解引用操作前,需进行判断;
  • 对动态开辟空间的越界访问;
  • 对非动态开辟的内存使用free释放;
  • 使用free释放一块动态开辟内存的一部分;
  • 对同一块动态内存多次释放;
  • 动态开辟内存忘记释放;忘记释放不在使用的动态开辟的空间,会造成内存泄露(切记一定要释放且要正确释放);
//动态内存的指针可能为NULL,在进行解引用操作前,需进行判断;
int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	//使用前,需判断p是否为空
	*p = 10;
	return 0;
}
//对动态开辟空间的越界访问;
int main()
{
	int* p = (int*)calloc(4, sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 0;
	}

	*(p+4) = 10;

	free(p);
	p = NULL;
	return 1;
}
//对非动态开辟的内存使用free释放;
int main()
{
	int i = 10;
	int* p = &i;
	free(p);
	p = NULL;
	return 1;
}
//使用free释放一块动态开辟内存的一部分;
int main()
{
	int* p = (int*)calloc(4, sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 0;
	}

	free(p + 2);
	p = NULL;
	return 1;
}
//对同一块动态内存多次释放;
int main()
{
	int* p = (int*)calloc(4, sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 0;
	}

	free(p);
	free(p);
	p = NULL;
	return 1;
}
//忘记释放动态开辟内存(内存泄漏)
int main()
{
	int* p = (int*)calloc(4, sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 0;
	}
	return 1;
}

注:栈区、堆区、静态区;

  • 栈区:局部变量、函数形参等;
  • 堆区:动态内存开辟;
  • 静态区(数据段):全局变量、静态变量static;

经典试题

//试题一
void GetMemory(char* p)
{
    //在堆区开辟内存
    //忘记释放,内存泄露
	p = (char*)malloc(100); 
}
int main()
{
	char* str = NULL;
	GetMemory(str); //函数调用结束p已销毁,并未更改str
	strcpy(str, "hello world");
	printf(str); 
    return 0;
}
//结果:异常访问冲突

//修改1
char* GetMemory(char* p)
{
	p = (char*)malloc(100); 
	return p;
}

int main( )
{
	char* str = NULL;
	str = GetMemory(str); 
	strcpy(str, "hello world");
	printf(str); 
    free(str);
    str = NULL;
    return 0;
}
//结果:hello world

//修改2
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}

int main()
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
    return 0;
}
//结果:hello world
//试题二
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
int main()
{
	//函数调用结束p已销毁,开辟的内存也还回系统
	char* str = NULL;
	str = GetMemory();
	printf(str);
    return 0;
}
//结果:烫烫烫烫烫烫烫烫

//修改
char* GetMemory(void)
{
    //static关键字避免销毁(栈区变为静态区)
	static char p[] = "hello world";
	return p;
}
int main()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
	return 0;
}

三,C/C++程序的内存开辟

栈区stack

  • 执行函数时,函数内局部变量可在栈上创建,函数执行结束时自动释放;
  • 栈区内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限;
  • 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据,返回地址等; 

堆区heap

  • 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收;
  • 分配方式类似于链表;

数据段(静态区)static

  • 存放全局变量、静态数据;
  • 程序结束后,由系统释放;

代码段

  • 存放函数体(类似成员函数和全局变量)的二级代码;
  • 常数;

完全自学C(干货) —— 动态内存管理_第2张图片

 四,柔性数组 — flexible array

  •  在C99,结构中的最后一个元素永许是未知大小的数组,即为柔性数组成员;
  • 结构中的数组,是大小可变的数组;
struct S
{
	char c;
	int arr[]; //或arr[0],此数组即为柔性数组成员
};

特点:

  • 柔性数组成员前面必须至少一个其他成员;
  • sizeof返回的大小不包括柔性数组内存;
  • 包含柔性数组成员的结构,用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小;
struct S
{
	char c;
	int arr[]; 
};

int main()
{
	printf("%d", sizeof(struct S)); //4
}
//会考虑对齐数
//柔性数组形式
struct S
{
	int n;
	int arr[]; 
};

int main()
{
	//sizeof(struct S)是分配给n的
	//10 * sizeof(int)是分配给arr的
	struct S* p = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 0;
	}
	
	free(p);
	p = NULL;
	return 1;
}

//指针形式
struct S
{
	int n;
	int* arr;
};

int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S));
	if (p == NULL)
	{
		perror("p");
		return 0;
	}

	p->arr = (int*)malloc(10 * sizeof(int));
	if (p->arr == NULL)
	{
		perror("p->arr");
		return 0;
	}

	free(p->arr); //需先释放
	p->arr = NULL;
	free(p);
	p = NULL;
	return 1;
}

优势:

  • 方便内存释放,多个malloc复杂且容易忘记释放;
  • 有利于访问速度,连续的内存空间,无内存碎片;

注:局部性原理

  • 空间局部性,下次会大概率会使用当前内存的周边区域;
  • 时间局部性,被引用过一次的存储器位置在未来会被多次引用(通常在循环中);

C语言结构体里的成员数组和指针

案例

  • 通讯录(使用动态内存动态增容)

你可能感兴趣的:(C,编程语言,c++,c语言,编程语言,c++)