❤️C语言动态内存管理库函数介绍❤️(下)

简介: 大家好!在实现动态通讯录的时候,我用到了malloc 和realloc动态申请内存,所以今天我想来和大家分享有关动态内存管理函数与柔性数组的相关知识。

💐2.C语言动态内存管理库函数应用



🌿🌿2.1常见相关笔试题


💐题目一:请问运行Test 函数会有什么样的结果?

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

💐分析:程序运行极大可能打印随机值


由于p是一个局部变量,因此当其出了函数GetMemory之后就会销毁,虽然在销毁之前将地址返回给str,但str维护的是一块野指针,因此str就会非法访问内存.

这类问题属于返回栈空间地址的问题。栈空间的地址不能轻易返回,因为一旦出了作用域之后就被销毁了。


💐题目二:请问运行Test 函数会有什么样的结果?

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

💐分析:程序运行打印随机值


函数GetMemory的形参为char* p,p为该函数的局部变量,作用域在函数内部,出了函数该变量就被销毁了,并且没有对申请好的内存进行释放。所以参数str传入函数GetMemory后,其值不会改变,仍为NULL,空地址是不能被用户访问修改的,因此程序崩溃。


💐题目三:请问运行Test 函数会有什么样的结果?

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

💐分析:该程序虽然会输出hello,但是是存在内存泄漏的,因为最后并没有释放申请的内存。

正确写法应该是:

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

💐题目四:请问运行Test 函数会有什么样的结果?

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

💐分析:由于free完没有置为NULL空指针,因此错误。

正确写法:

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


🌿🌿2.2C/C++语言中的内存开辟


2f37a9c5d03949b5ba9a79bab5561453.png

栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行 结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但 是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、 返回地址等。


堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分

配方式类似于链表。


数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。


代码段:存放函数体(类成员函数和全局函数)的二进制代码。


🌿🌿2.3柔性数组


也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

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


🌺🌺🌺2.3.1柔性数组特点与使用


typedef struct st_type
{
 int i;
 int a[];  //柔性数组成员
}type_a;

💐注意事项:

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

💐举个栗子:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

这里输出的是4,不包括柔性数组的大小。


💐为柔性数组开辟空间

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++) {
 p->a[i] = i; }
free(p);

这里使用malloc为柔性数组开辟空间,100*sizeof(int)就是开辟了柔性数组的空间。

这样柔性数组成员a,相当于获得了100个整型元素的连续空间。


🌺🌺🌺2.3.2柔性数组的优点


💐第一个好处是:方便内存释放


如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回 给用户。

用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这事。如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。


💐 第二个好处是:这样有利于访问速度.


连续的内存有益于提高访问速度,也有益于减少内存碎片。


相关文章
|
4天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
19 6
|
18天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
29 3
|
17天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
23天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
23天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4
|
20天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
18 0
|
20天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
18 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
32 3
|
24天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
33 10
|
28天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。