【C进阶】动态内存管理

码字不易,对你有帮助 点赞/转发/关注 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新

想看更好的排版可以阅读原文
点击阅读原文

思维导图


目录


正文


零 简单了解内存区域划分

一 动态内存函数

1.1 malloc

malloc -> memory allocate

void* malloc (size_t size)

size_t 类型就是 unsigned long

库函数:stdlib.h

解释:

分配 size 字节的未初始化内存。

若分配成功,则返回为任何拥有基础对齐的对象类型对齐的指针。

size 为零,则 malloc 的行为是实现定义的。例如可返回空指针。亦可返回非空指针;但不应当解引用这种指针,而且应将它传递给 free 以避免内存泄漏。

**参数:**size - 要分配的字节数

返回值:

成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free()realloc() 解分配返回的指针。

失败时,返回空指针。

英文文档:

Allocate memory block

Allocates a block of size bytes of memory, returning a pointer to the beginning of the block.

The content of the newly allocated block of memory is not initialized, remaining with indeterminate values.

If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced.

Parameters

size

Size of the memory block, in bytes.
size_t is an unsigned integral type.

Return Value

On success, a pointer to the memory block allocated by the function.

The type of this pointer is always void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

If the function failed to allocate the requested block of memory, a null pointer is returned.

例1:malloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int size;
	printf("请输入元素个数:");
	scanf("%d", &size);
	int* arr = (int*)malloc(size * sizeof(int));
	//内存申请失败返回 空指针
	if (arr == NULL) {
		printf("内存申请失败!\n");
		return 0;
	}
	for (int i = 0; i < size; i++) {
		arr[i] = i;
		printf("%d\n", i);
	}

	free(arr);
	return 0;
}

1.2 free

void free( void* ptr )

**头文件:**stdlib.h

解释:

解分配之前由 malloc()calloc()realloc() 分配的空间

ptr 为空指针,则函数不进行操作。

ptr 的值不等于之前从 malloc()calloc()realloc() 返回的值,则行为未定义。

ptr 所指代的内存区域已经被解分配,则行为未定义,即是说已经以ptr 为参数调用 free()realloc() ,而且没有后继的 malloc()calloc()realloc() 调用以 ptr 为结果。

若在 free() 返回后通过指针 ptr 访问内存,则行为未定义(除非另一个分配函数恰好返回等于 ptr 的值)。

参数: ptr - 指向要解分配的内存的指针

返回值:

**注意:**此函数接收空指针(并对其不处理)以减少特例的数量。不管分配成功与否,分配函数返回的指针都能传递给 free()

英文文档

Deallocate memory block

A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations.

If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior.

If ptr is a null pointer, the function does nothing.

Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location.

Paramaters

ptr

Pointer to a memory block previously allocated with malloc, calloc or realloc.

Return Value

none

If ptr does not point to a memory block previously allocated with malloc, calloc or realloc, and is not a null pointer, it causes undefined behavior.

1.3 calloc

void* calloc( size_t num, size_t size )

**头文件:**stdlib.h

解释:

num 个对象的数组分配内存,并初始化所有分配存储中的字节为零。

若分配成功,会返回指向分配内存块最低位(首位)字节的指针,它为任何类型适当地对齐。

size 为零,则行为是实现定义的(可返回空指针,或返回不可用于访问存储的非空指针)。

参数:

num - 对象数目

size - 每个对象的大小

返回值:

成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free()realloc() 解分配返回的指针。

失败时,返回空指针。

注意:

因为对齐需求的缘故,分配的字节数不必等于 num*size

初始化所有位为零不保证浮点数或指针被各种初始化为 0.0 或空指针(尽管这在所有常见平台上为真)。

英文文档:

void* calloc (size_t num, size_t size);

Allocate and zero-initialize array

Allocates a block of memory for an array of num elements, each of them size bytes long, and initializes all its bits to zero.

The effective result is the allocation of a zero-initialized memory block of (num*size) bytes.

If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced.

Parameters

  • num

Number of elements to allocate.

  • size

Size of each element.

size_t - is an unsigned integral type.

Return Value

On success, a pointer to the memory block allocated by the function.

The type of this pointer is always void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

If the function failed to allocate the requested block of memory, a null pointer is returned.

例2:calloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int* p1 = (int*)calloc(4, sizeof(int));//分配并清零 4 个 int 的数组
	int* p2 = (int*)calloc(1, sizeof(int[4]));//等价,直接命名数组类型
	int* p3 = (int*)calloc(4, sizeof *p3);//等价,免去重复类型名

	if (p2) {
		for (int n = 0; n < 4; n++)
			printf("p2[%d] == %d\n", n, p2[n]);
	}

	free(p1);
	free(p2);
	free(p3);

	return 0;
}

1.4 realloc

英文文档:

void* realloc (void* ptr, size_t size)

Reallocate memory block

Changes the size of the memory block pointed to by ptr.

The function may move the memory block to a new location (whose address is returned by the function).

The content of the memory block is preserved up to the lesser of the new and old sizes, even if the block is moved to a new location. If the new size is larger, the value of the newly allocated portion is indeterminate.

In case that ptr is a null pointer, the function behaves like malloc, assigning a new block of size bytes and returning a pointer to its beginning.

C90:

Otherwise, if size is zero, the memory previously allocated at ptr is deallocated as if a call to free was made, and a null pointer is returned.

C99/C11:

If size is zero, the return value depends on the particular library implementation: it may either be a null pointer or some other location that shall not be dereferenced.

If the function fails to allocate the requested block of memory, a null pointer is returned, and the memory block pointed to by argument ptr is not deallocated (it is still valid, and with its contents unchanged).

Parameter

ptr

Pointer to a memory block previously allocated with malloc, calloc or realloc.
Alternatively, this can be a null pointer, in which case a new block is allocated (as if malloc was called).

size

New size for the memory block, in bytes.
size_t is an unsigned integral type.

Return Value

A pointer to the reallocated memory block, which may be either the same as ptr or a new location.
The type of this pointer is void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

C90:

A null-pointer indicates either that size was zero (an thus ptr was deallocated), or that the function did not allocate storage (and thus the block pointed by ptr was not modified).

C99:

A null-pointer indicates that the function failed to allocate storage, and thus the block pointed by ptr was not modified.

例3:realloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int* pa = (int*)malloc(3 * sizeof(int));
	if (pa) {
		printf("%zu bytes allocated. Storing ints:\n", 3 * sizeof(int));
		for (int i = 0; i < 3; i++)
			printf("%d\n", pa[i] = i);
	}
	
	int* pb = (int*)malloc(10 * sizeof(int));
	if (pb) {
		printf("%zu bytes allocated. First 3 ints:\n", 10 * sizeof(int));
		for (int i = 0; i < 3; i++)
			printf("%d\n", pb[i] = i);
	}
	else { // 如果 realloc 返回 NULL,表示扩容失败,我们需要 free pa指向的地址
		free(pa);
	}
	//如果成功,free pb即可,如果 pb 与 pa 指向的内容不同,那么 pa 就已经被释放过了
	free(pb);

	return 0;
}
例3.1:realloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int input, i;
	int count = 0;
	int* numbers = NULL;

	do {
		printf("Ener a number: ");
		scanf("%d", &input);
		count++;

		numbers = (int*)realloc(numbers, count * sizeof(int));

		if (numbers != NULL) {
			numbers[count - 1] = input;
		}
		else {
			free(numbers);
			printf("Error reallocating memory\n");
			exit (1);
		}

	} while (input != 0);

	printf("%zu bytes allocated. Elements as follow:\n", count * sizeof(int));
	for (i = 0; i < count; i++) {
		printf("%d ", numbers[i]);
	}

	free(numbers);

	return 0;
}

1.5 malloc 造成内存泄漏的典例

典例1:由 return 造成的没有 free
#include<stdio.h>
#include<stdlib.h>

int* Func() {

	int* p = (int*)malloc(sizeof(int) * 10);
	if (p == NULL) {
		return NULL;
	}
	//下面是业务逻辑代码:
	if (cond1) {
		return p;//满足条件就return ,p 没有 free
	}
	if (cond2) {
		return p;//同上
	}
    //执行一些操作
	do_something;

	free(p);

}

int main(void) {

	int* a = Func();

	return 0;
}
典例2:malloc 后没有 free,再次 malloc
int* Func() {
	
	int* p = (int*)malloc(sizeof(int) * 10);
	p = (int*)malloc(sizeof(int*) * 20);//第一次 malloc 的内存没有 free
	if (p) {
		return NULL;
	}
	do_something;

	free(p);

}
典例3:指针运算后改变造成 free 失败
void test(){
    int* p = (int*)malloc(4 * sizeof(int));
    p++;
    free(p);
}
典例4:多次 free 同一块内存
void test(){
    int* p = (int*)malloc(4 * sizeof(int));
    free(p);
    free(p);
}
典例5:对 NULL 进行解引用操作
void test(){
	int *p = (int *)malloc(INT_MAX/4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}
典例6:对动态开辟空间的越界访问
void test(){
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if(NULL == p){
	exit(EXIT_FAILURE);
	}
	for(i=0; i<=10; i++){
	*(p+i) = i;//当i是10的时候越界访问
	}
	free(p);
}

二 笔试题

1

void GetMemory(char* p)
{
    //形参是实参的拷贝,形参指针不会改变实参指针的指向
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf("%s", str);
}
//请问运行Test 函数会有什么样的结果?
程序崩溃
  1. malloc 之后要判空
  2. 没有 free

2

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
    //函数结束,p 的地址内的内容是未知的
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf("%s", str);
}

3

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf("%s", str);
}
//没有 free 

4

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
// 访问非法内存
// free 操作不会改变 str 内容

三 柔性数组

柔性数组(flexible array):C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

1. 特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

2. 使用方法

1. 定义结构体
typedef struct Test {
	int i;
	int a[];// 也可写成 int a[0]
}Test;
2. 使用
int main(void) {

	Test* test = (Test*)malloc(sizeof(Test) + sizeof(int) * 100);
    
	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	free(test);
	return 0;
}
3. 反例

如果我们不用 “柔性数组”,而这样 定义结构体和使用 可不可以实现自定义数组大小呢?

typedef struct Test {
	int i;
	int* a;// 修改
}Test;

int main(void) {

	Test* test = (Test*)malloc(sizeof(Test) + sizeof(int) * 100);

	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	
    free(test);
	return 0;
}

这样显然是不行的,那我们应该如何写呢?

正确的写法是 malloc 两次

typedef struct Test {
	int i;
	int* a;
}Test;

int main(void) {

	Test* test = (Test*)malloc(sizeof(Test));
	test->a = (int*)malloc(sizeof(int) * 100);

	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	
	free(test->a);
	free(test);

	return 0;
}

malloc 两次 free 也得两次,所以这种写法还是十分麻烦的。

补:printf(str)

int main(void) {

	char* str = "Hello";

	printf(str);

	return 0;
}

这个程序会正常输出 str 的内容。因为 char* 的类型,而 printf format 的格式是 char const* const ,C语言的对类型之间的标准比较模糊,所以没有报错,起始这种写法类似于:

printf("Hello");

参考资料:cppreference.com cplusplus.com

在 Github 上看更全的目录:

https://github.com/hairrrrr/C-CrashCourse

以后的这个系列的代码都会上传上去,欢迎 star


以上就是本次的内容。

如果文章有错误欢迎指正和补充,感谢!

最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我可以在后面的文章加上你们的真知灼见​​。

关注我,看更多干货!

我是程序圆,我们下次再见。

发布了65 篇原创文章 · 获赞 226 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_44954010/article/details/105069092