初识动态内存分配

目录

为什么会存在动态内存分配:

malloc:

free:

calloc:

realloc:

注意事项:

攻破经典易错题:

题目一:

 存在以下两种方式进行修改:

1.利用二级指针进行修改:

2.利用返回指针的方式进行修改:

题目二:

题目三:

题目四:

 柔性数组:

柔性数组的优势:

 总结:


为什么会存在动态内存分配:

动态内存分配可以提高程序的灵活性和效率。使用动态内存分配可以在程序运行时根据需要分配和释放内存空间,而不是在程序编译时分配固定的内存空间。这样可以避免浪费内存空间和提高内存利用率。动态内存分配还可以支持动态数据结构,例如链表和树等,这些数据结构的大小不确定,可以在运行时根据实际需要调整大小。此外,动态内存分配还可以避免栈溢出和内存泄漏等问题。因此,动态内存分配在编写大型程序时非常有用。

目前我们学习有2种申请内存的方式

int a = 10;
int arr[] = {1, 2, 3, 4, 5};

通过上述两种方法实现的内存申请,申请到的内存空间固定,不够灵活。

接下来我们就来介绍介绍实现动态内存开辟的一系列函数:

malloc:

malloc()函数是C语言中用于分配动态内存的函数,具有以下原型:

void *malloc(size_t size);

该函数返回一个指向分配内存的指针,如果没有足够的内存空间,则返回NULL。参数size表示要分配的内存大小(以字节为单位),可以是任意整数或表达式。

使用malloc()函数时,需要注意以下几点:

  1. malloc()函数返回的指针指向的是一块连续的、未初始化的内存空间,可以通过类型转换将其转换为所需类型的指针。
  2. 在使用malloc()函数申请内存后,需要在不需要使用该内存时将其释放掉,否则会造成内存泄漏。
  3. 释放内存时使用free()函数,其原型为:
void free(void *ptr);

其中ptr是之前使用malloc()函数返回的指针,如果该指针为NULL,则free()函数不执行任何操作。

下面是一个使用malloc()函数分配和释放内存的示例代码:

#include 
#include 

int main() {
    int *p;
    p = (int*)malloc(sizeof(int) * 10); // 分配10个整数的内存空间
    if (p == NULL) {
        printf("Error: failed to allocate memory.\n");
        return 1;
    }
    for (int i = 0; i < 10; i++) {
        *(p + i) = i; // 对内存空间进行赋值
    }
    for (int i = 0; i < 10; i++) {
        printf("%d ", *(p + i)); // 输出内存空间的值
    }
    free(p); // 释放内存空间
    return 0;
}

在我们释放malloc开辟的空间时,我们存在通过free来释放——主动释放

也有通过程序退出后,malloc申请的空间也会被操作系统收回——被动释放。

因此为了安全起见,我们最好首选主动释放。

free:

free函数是一种用于释放在堆上分配的内存块的函数。该函数接受一个指向已分配内存块的指针作为参数,并将该内存块的使用计数减少1。如果减少后计数为0,则释放该内存块。这个函数是C库函数,定义在stdlib.h头文件中。在使用动态内存分配函数malloc或calloc分配内存后,必须使用free函数来释放内存,以避免内存泄漏。

如果我们需要释放掉一块我们自己开辟出来的空间,

我们可以进行一下操作:

free(p);
p = NULL;

要注意的是,free(p)之后的p = NULL必不可少!!!

calloc:

calloc() 函数是 C 语言标准库中的函数,用于动态分配内存空间,并将内存空间的每个字节初始化为0。

calloc() 函数的声明如下:

void* calloc(size_t num, size_t size);

其中,第一个参数 num 表示需要分配的元素个数,第二个参数 size 表示每个元素的大小。calloc() 函数返回指向分配的内存空间的指针,如果分配失败则返回 NULL。

calloc() 函数与 malloc() 函数类似,但是 calloc() 在分配内存空间时会将空间中的每个字节都初始化为0,而 malloc() 函数不会做这个操作。因此,如果需要使用一块全为0的内存空间,可以使用 calloc() 函数来分配内存。

使用 calloc() 函数的例子如下:

#include 
#include 

int main()
{
    int *ptr;
    int i, n;

    printf("Enter number of elements: ");
    scanf("%d", &n);

    ptr = (int*)calloc(n, sizeof(int));

    if (ptr == NULL)
    {
        printf("Memory not allocated.\n");
        exit(0);
    }
    else
    {
        printf("Memory successfully allocated using calloc.\n");

        for (i = 0; i < n; ++i)
        {
            printf("Enter element %d: ", i+1);
            scanf("%d", &ptr[i]);
        }

        printf("The elements you entered are: ");
        for (i = 0; i < n; ++i)
        {
            printf("%d ", ptr[i]);
        }
    }

    free(ptr);
    return 0;
}

在上述示例中,我们使用 calloc() 函数来分配一段大小为 n * sizeof(int) 的内存空间,并将其赋值给 ptr 指针。然后,我们输入 n 个整数,将其存储到分配的内存空间中,并输出这些整数,最后释放已分配的内存空间。

realloc:

C语言中的realloc函数是动态内存分配函数之一,其功能是用于重新分配已分配内存的大小,实现动态内存管理。

函数定义:

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

参数说明:

  • ptr:需要重新分配内存的指针地址。
  • size:重新分配的内存大小(字节长度)。

函数返回值:

  • 若重新分配成功,则返回新分配内存的指针,且原有指针指向的内存已经被释放;
  • 若重新分配失败,则返回NULL,原有指针所指向的内存不会被释放。

使用realloc函数时需要注意以下几点:

  • 如果ptr为NULL,则realloc的操作效果等价于malloc(size),即它将分配一个新的内存块并返回其指针;
  • 如果size为0,则realloc的操作效果等价于free(ptr),即它将释放原有的内存块;
  • 如果ptr不为NULL且size不为0,则realloc的操作效果是将原有内存块中的数据载入新分配的内存块中,原有内存块将被释放。
  • 如果realloc返回NULL,则说明重新分配内存失败,应该停止程序运行并进行相关的错误处理。

使用realloc函数时应该注意安全性和合理性,避免出现内存泄漏、内存重叠等问题。

 这里要注意的一点是:

我们在实现realloc开辟空间时,要清楚的知道realloc开辟空间也会失败,失败则会返回NULL,

所以我们应当创建一个临时指针变量,并作出判断再讲临时指针变量赋值到原指针处。

如果我们在使用realloc的时候,遇到“后面的空间被占用了”这种情况时。

此时realloc函数就会去找一块新的并且足够的空间,一次性将空间直接开辟好。

并将旧空间里的数据直接拷贝到新的空间,并且释放掉旧空间的数据和空间本身,

再返回新的空间的地址。 

注意事项:

在我们使用上述所讲的函数时,要注意一下:

1.注意千万不要对NULL直接进行解引用操作。

2.注意一定不要越界访问。

3.注意不可以仅仅使用free释放掉一部分。

4.对同一块空间多次释放。

5.不可以不释放空间。

6.注意不可以对非动态内存进行释放。

攻破经典易错题:

题目一:

//T1
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}

void test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

以上代码存在以下问题:

1.未对str进行free释放。

2.存在内存泄漏。

3.程序对NULL直接进行解引用操作,程序崩溃!

初识动态内存分配_第1张图片

 存在以下两种方式进行修改:

1.利用二级指针进行修改:
//修改:
void GetMemory(char**p)
{
	*p = (char*)malloc(100);
}

void test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");

	printf(str);

    //释放str开辟的空间
	free(str);
	str = NULL;
}
2.利用返回指针的方式进行修改:
char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;
}

void test(void)
{
	char* str = NULL;

	str = GetMemory(str);
	strcpy(str, "hello world");

	printf(str);
	
	//释放str开辟的空间
	free(str);
	str = NULL;
}

以上的输出结果都为:

题目二:

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

void test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

以上存在以下问题:

函数吊桶结束后,str变为野指针。

此时str指向的空间已被释放 

那么此时返回栈空间出现问题!

初识动态内存分配_第2张图片

题目三:

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}

void tests(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

以上存在的问题主要未使用free进行释放,

因此存在内存泄漏的问题。

题目四:

 

//题目四
void test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcat(str, "world");
		printf(str);
	}
}

这里存在的问题主要是 

1.对内存的非法访问。

2.free完后未将str设为NULL。

修改后应当如下:

 

//题目四
void test(void)
{
	char* str = (char*)malloc(100); 
	if (str != NULL)
	{
		strcpy(str, "hello");
		strcat(str, "world");
		printf(str);
	}
	else
	{
		perror("str->malloc");
		return;
	}
	free(str);
	str = NULL;
}

 柔性数组:

C语言中的柔性数组指的是一种特殊的数组,它可以在定义时不指定数组大小,在运行时动态分配内存。它的定义格式为:

struct my_struct {
    int size;
    int data[]; // 柔性数组
};

在这个定义中,data[]就是一个柔性数组,它没有指定大小,因此可以在运行时根据需要动态分配内存。注意,在柔性数组后面不能再有其他成员。

在使用柔性数组时,我们可以通过以下方式进行动态内存分配:

struct my_struct *p = malloc(sizeof(struct my_struct) + n * sizeof(int));
p->size = n;

这里我们通过malloc函数动态分配了一个结构体my_struct和一个大小为n的int数组,p指向结构体的首地址。由于柔性数组不占据结构体内存空间,因此可以使用sizeof(struct my_struct) + n * sizeof(int)来计算需要分配的内存大小。通过p->size来记录数组的大小。

在使用柔性数组时,我们可以像普通数组一样访问其中的元素,例如:

p->data[0] = 1;
p->data[1] = 2;
p->data[2] = 3;

需要注意的是,柔性数组只能用于结构体的最后一个成员,而且在定义时必须省略数组大小。同时,在使用柔性数组时,需要注意内存对齐问题,否则可能会导致程序出错。

 当空间不够时,可以使用以下代码:

struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40);

if(ptr != NULL)
{    
    ps = ptr;
}
else
{
    perror("relloc");
    return 1;
}

 

柔性数组的优势:

  1. 更灵活的内存管理:柔性数组始终分配恰好足够的内存空间,不会浪费内存。同时,由于可以在运行时动态分配内存空间,可以避免在编译时限制数组长度。

  2. 更高效的代码:使用柔性数组可以减少代码中的重复代码和计算,因为只需要计算数组的实际长度一次,并且可以像普通数组一样方便地访问和操作。

  3. 更方便的代码编写:使用柔性数组可以使代码更加简洁和易于理解,因为不需要手动处理内存分配和释放,也不需要手动处理数组长度。

总的来说,柔性数组是一种方便、高效和灵活的内存管理工具,特别适用于需要动态分配数组空间的情况。

 总结:

以上就是关于动态内存的内容了,学习完后下来可以自己动手在VS中使用动态内存创建创建空间,熟练使用它们。

在接下来的blog中我们将会实现通讯录的创建运用动态内存管理。

所以在此基础上,我们最好可以进行复习结构体和动态内存的相关知识。

记住:

“坐而言不如起而行”

Action speak louder than words!

以下是我的Gitee:

dynamic_memory_question_CSDN/dynamic_memory_question_CSDN/test.c · 无双/test_c_with_X1 - Gitee.com

你可能感兴趣的:(算法,数据结构,c语言,经验分享,笔记)