动态内存管理

目录

一、动态内存函数的介绍

1.1 malloc

1.2 free

1.3 calloc

1.4 realloc

二、常见的动态内存错误

2.1 对NULL指针的解引用

2.2 对动态内存开辟空间的越界访问

2.3 对非动态开辟内存使用free释放

2.4 使用free释放一块动态开辟内存的一部分

2.5 对同一块动态内存多次释放

2.6 动态开辟内存忘记释放(内存泄漏)

三、经典的笔试题

3.1 题目一

3.2 题目二

3.3 题目三

3.4 题目四

四、柔性数组

4.1 特点


我们之前学到的在内存开辟空间是创建一个变量指定它的类型来确定开辟的大小:

int a;//在栈上开辟4个空间
int b[10];//在栈上开辟40个空间

但有时开辟的空间大小只有在程序运行起来才知道,或是要改变空间的大小,这是就需要动态内存才能实现。

一、动态内存函数的介绍

1.1 malloc

头文件:<stdlib.h> 

void* malloc (size_t size):

        这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针; size是这块空间的大小(字节为单位);如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

        返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

        如果开辟成功,则返回一个指向开辟好空间的指针。
        如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

使用动态内存开辟的空间最好在开辟后判断是否为NULL这样使用起来更加安全:

int main()
{
	int* arr = (int*)malloc(sizeof(int) * 10);
	if (arr == NULL)
	{
		perror("malloc");
        return 1;
	}
	//arr操作
	//...
	free(arr);
    arr=NULL;
	return 0;
}

1.2 free

动态内存开辟是像内存申请,既然申请的那就需要释放来归还那块空间,如果没有及时释放那累计多了就可能会影响到其他程序的使用。

void free (void* ptr):

        ptr:要释放的空间。

        如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
        如果参数 ptr 是NULL指针,则函数什么事都不做。

在程序退出后动态申请的空间虽然后主动释放,但我们还是要养成良好主动来释放。释放完的指针就会成为野指针最好将他置为空指针。

1.3 calloc

void* calloc (size_t num, size_t size):

        num:开辟空间的元素个数。

        size:开辟元素的大小。

calloc是会将开辟空间的内容全部初始化为0,malloc不会。

malloc(上面代码举例来看):

calloc:

int main()
{
	int* arr = (int*)calloc(10,sizeof(int));
	if (arr == NULL)
	{
		perror("malloc");
        return 1;
	}
	//arr操作
	//...
	free(arr);
	arr = NULL;
	return 0;
}

1.4 realloc

之前说过动态内存是可以改变申请空间的大小,realloc就是来实现这个功能。

void* realloc (void* ptr, size_t size)
        ptr:要调整的内存地址。
        size:调整之后新大小(不是又申请了几个字节,是调整后整体有多大)。
        返回值为调整之后的内存起始位置。
        realloc在调整内存空间的是存在两种情况:
                情况1:原有空间之后有足够大的空间,直接在后面追加。

                情况2:原有空间之后没有足够大的空间,会在其它地方申请一块足够的空间,原空间的数据也会移动到新空间上。

int main()
{
	int* arr = (int*)calloc(10,sizeof(int));
	if (arr == NULL)
	{
		perror("malloc");
        return 1;
	}
	int i = 0;
	int* p = NULL;
	p = (int*)realloc(arr, sizeof(int) * 15);
	if (p != NULL)//防止申请失败
		arr = p;
	for (i = 0; i < 15; i++)
	{
		arr[i] = i;
	}
	for (i = 0; i < 15; i++)
	{
		printf("%d ", arr[i]);
	}
	free(arr);
	arr = NULL;
	return 0;
}

二、常见的动态内存错误

2.1 对NULL指针的解引用

void test()
{
	int* p = (int*)malloc(INT_MAX * 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

申请过大时就会返回NULL,所以需要判断。

2.2 对动态内存开辟空间的越界访问

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);
}

动态开辟和数组一样是不能越界访问的。

2.3 对非动态开辟内存使用free释放

void test()
{
    int a = 10;
    int *p = &a;
    free(p);//ok?
}

这时程序会直接运行不下去,free只能释放动态开辟的内存。

2.4 使用free释放一块动态开辟内存的一部分

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

这个程序也不会运行。

2.5 对同一块动态内存多次释放

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}

第一次释放过后p就是野指针了,第二次对野指针进行了操作,p这时也不在是动态内存。

2.6 动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);
}

程序不结束vs也无法帮我们释放p,p就会一直占用那块空间导致内存泄漏,所以动态开辟的空间一定要主动释放,并且正确释放。

三、经典的笔试题

3.1 题目一

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

运行Test:

        GetMemoiy(str),将str的值NULL传递给p,p开辟空间后并没有返回,str是接受不到的传参时只传了形参,后面是对NULL的操作会报错,并且p开辟的内存是没有释放的。

3.2 题目二

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

运行Test:

        这里GetMemory返回了数组p开辟的地址,数组创建的内存是在栈区开辟的空间出了函数就将那块数据销毁了,指向那块空间的地址也不在归编译器管理,将p返回给str,str就是个野指针了。

3.3 题目三

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

运行Test:

        &str传递的是实参给了p,p以char**接收也是没有问题,为*p开辟一块空间也是在为str开辟,后面对str的使用也没问题,但是它并没有对str进行释放,可能会导致内存泄漏。

3.4 题目四

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

运行Test:

        

运行会发现屏幕上依旧打印了“world”,但在free释放了str后str已经成了野指针,后面虽然运行了,但我们也要避免这样使用。

四、柔性数组

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

struct s
{
	int a;
	char b;
	int c[];
};

4.1 特点

结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。

struct s
{
	int a;
	char b;
	int c[];
};
int main()
{
	printf("%d", sizeof(struct s));
	return 0;
}

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

struct s
{
	int a;
	char b;
	int c[];
};
int main()
{
	struct s* p = (struct s*)malloc(sizeof(struct s) + sizeof(int) * 10);//给c开辟10个int型元素
	if (p == NULL)
	{
		perror("malloc");
        return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p->c[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p->c[i]);
	}
    free(p);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值