C语言动态内存管理(malloc, calloc,realloc)详解

目录

导读:

一、动态内存的优点

二、动态内存的建立与分配

1. 用malloc函数开辟动态存储区

2. free函数释放动态存储区

3. 实际操作

4. calloc 函数开辟动态存储区

4.1 calloc基本概念

4.2 实际操作

5. realloc函数重新分配动态存储 

5.1 realloc基本概念

5.2 实际操作

三、常见的动态内存错误

1. 对NULL指针的解引用操作

2. 对动态开辟空间的越界访问

四、深层次思考

1. 代码:

思考:

错误修改:

2. 修改


导读:

动态内存管理是在程序运行时动态地分配和释放内存空间,可以让程序更加灵活和高效。

本章节会对malloc、calloc、realloc以及内存的释放——free等进行详细介绍

也会解答一些容易思考错误的题目

一、动态内存的优点

  1. 灵活性:动态内存管理使程序能够根据需要动态地分配和释放内存,从而提高程序的灵活性和可扩展性。

  2. 内存资源利用率:动态内存管理可以优化内存使用,避免浪费内存资源。

  3. 可靠性:动态内存管理可以减少内存泄漏和内存溢出等问题,从而提高程序的稳定性和可靠性。

  4. 程序性能:动态内存管理可以提高程序的性能,因为程序可以在需要时分配内存,而不是在程序执行时一次性分配所有内存。

  5. 多任务处理:动态内存管理可以使多任务处理更加容易,因为程序可以在运行时动态地分配和释放内存,从而避免多个任务之间的内存冲突。

对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数。

二、动态内存的建立与分配

1. 用malloc函数开辟动态存储区

void* malloc (size_t size);

其作用是在内存的动态存储区扽配一个长度为size的 连续空间。

此函数的值(“即返回值”)是所分配区域的第一个字节的地址。

或者说此函数是一个指针型函数,返回的指针指向该分配区域的第一个字节

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2. free函数释放动态存储区

void free (void* ptr);

作用是释放动态分配的内存空间。在使用malloc、calloc等函数分配内存空间后,需要使用free函数释放已经使用完的内存空间,以便其他程序或系统使用其内存空间。

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

 注意:

使用free函数释放已经释放的内存空间会导致不可预知的错误,如内存泄漏、内存破坏等问题。

因此,在使用free函数释放动态分配的内存空间时,需要慎重考虑,确保操作正确无误。

3. 实际操作

int main()
{
    int i, n;
    char* buffer;
    printf("How long do you want the string? \n");
    scanf("%d", &i);
    buffer = (char*)malloc(i + 1);
    if (buffer == NULL)//判断buffer指针是否为空
    {
        exit(1);//如果为空则退出
    }
    for (n = 0; n < i; n++)
    {
        buffer[n] = rand() % 26 + 'a';//随机生成
    }
    buffer[i] = '\0';
    printf("Random string: %s\n", buffer);
    free(buffer);//释放内存
    return 0;
}

生成一个长度由用户指定的字符串,并用字母字符填充。此字符串的可能长度仅受malloc可用内存量的限制。

运行结果:

4. calloc 函数开辟动态存储区

4.1 calloc基本概念

void* calloc (size_t num, size_t size);

 calloc函数是C语言中的一个动态内存分配函数之一,它可以动态地分配内存并初始化为0。

num是要分配的元素个数,size是每个元素的字节数。它会在内存中分配num * size字节的连续空间

  • 如果分配成功,该函数返回一个指向分配的内存块的指针
  • 如果分配失败,则返回 NULL

与 malloc 函数不同的是,calloc 函数会在内存分配时清零,因此在某些情况下会比 malloc 更加安全,尤其是在处理字符串等需要清零的数据时。 

但是,由于 calloc 会在分配内存时清零,因此在内存空间较大的情况下,使用 calloc 会比 malloc 的效率低一些。

4.2 实际操作

int main()
{
    int i, n;
    int* pData;
    printf("要输入的数字数量: \n");
    scanf("%d", &i);
    pData = (int*)calloc(i, sizeof(int));
    if (pData == NULL)
    {
        exit(1);
    }
    for (n = 0; n < i; n++)
    {
        printf("输入数字 #%d: ", n + 1);
        scanf("%d", &pData[n]);
    }
    printf("您已输入: ");
    for (n = 0; n < i; n++)
    {
        printf("%d ", pData[n]);
    }
    free(pData);
    return 0;
}

这个程序只是存储数字,然后打印出来。但每次执行程序时,它存储的项目数量都可以调整,因为它在运行时分配了所需的内存。

5. realloc函数重新分配动态存储 

5.1 realloc基本概念

void* realloc (void* ptr, size_t size);

realloc函数在C语言中用于重新分配已经存在的内存空间的大小

ptr表示要重新分配大小的内存指针,size表示重新分配的内存大小

  • 如果分配成功会返回一个指针,指向重新分配后的内存空间
  • 如果分配不成功,则返回NULL
  • 如果重新分配的大小比原来的大,则增加的内存空间会保留原来的值
  • 如果重新分配的大小比原来的小,则会将多余的部分释放掉 

 注意:

使用realloc函数重新分配内存时,如果原来的内存空间存在指针或者其他相关的数据,需要在重新分配内存之前进行拷贝或者修改,以免出现内存覆盖的问题。

realloc在调整内存空间的是存在两种情况:

  1. 原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
  2. 原有空间之后没有足够大的空间,在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

5.2 实际操作

程序会提示用户输入数字,直到输入一个零字符。每次引入新值时,数字指向的内存块都会增加int的大小

int main()
{
    int input, n;
    int count = 0;
    int* numbers = NULL;
    int* more_numbers = NULL;
    do {
        printf("输入整数值(0结束): ");
        scanf("%d", &input);
        count++;
        more_numbers = (int*)realloc(numbers, count * sizeof(int));
        if (more_numbers != NULL) 
        {
            numbers = more_numbers;
            numbers[count - 1] = input;
        }
        else 
        {
            free(numbers);
            puts("错误(重新)分配内存");
            exit(1);
        }
    } while (input != 0);

    printf("输入的数字: ");
    for (n = 0; n < count; n++)
    {
        printf("%d ", numbers[n]);
    }
    free(numbers);
    return 0;
}

三、常见的动态内存错误

1. 对NULL指针的解引用操作

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

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

四、深层次思考

1. 代码:

请问下面代码中,运行Test 函数会有什么样的结果?

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

思考:

在我们的猜想中,返还的应该是hello world,结果却是程序崩溃

在这里*p指向的并不是我们认为的str的地址,str是指针变量,传递的是空指针

*p指向的还是NULL,所以malloc函数开辟的空间给了NULL,程序对NULL解引用操作,从而导致程序崩溃 

错误修改:

博主过了两天发现上面的讲解是有问题的,程序崩溃的问题并不是如上述所说的那样,我们再来看一次代码

我们在把 str 传给GetMemory这个函数,并用字符指针 p 来接收,p指向的也是NULL,然后接下来往后看,malloc开辟一块内存空间,p来接收,这时p指向的是malloc开辟空间的这个地址,而不是NULL

这时我们返回到test函数,malloc这块空间并没有被释放,p出了GetMemory这个函数,进入到test函数时就已经被销毁了,这时我们已经找不到malloc所开辟空间的地址

 

再到strcpy这里,str还是一个空指针,而要对str塞入内容,就要对NULL解引用操作,所以才导致程序崩溃。 

2. 修改

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

想要扩大str的地址,只需要把str的地址传参过去,也就是&str,用二级指针 **p 来接收 str 的地址,相对的扩大的自然是str的内存空间

猜你喜欢

转载自blog.csdn.net/qq_64818885/article/details/133390336