C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(下)

简介: C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free

C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(中):https://developer.aliyun.com/article/1513205

5. C/C++程序的内存开辟

C/C++程序内存分配的几个区域:

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,

函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,

效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、

函数参数、返回数据、返回地址等。

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

分配方式类似于链表。

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

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

内存区域划分图:

有了这幅图,我们就可以更好的理解在前面讲的static关键字修饰局部变量的例子了。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。

但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,

直到程序结束才销毁,所以生命周期变长。

栈区的特点:在上面创建的变量出了作用域就销毁。

数据段的特点:在上面创建的变量直到程序结束才销毁。


6. 柔性数组 flexible array

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


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


定义:柔性数组(Flexible Array),又称可变长数组。一般数组的长度是在编译时确定,而柔性数组对象的长度在运行时确定。在定义结构体时允许你创建一个空数组(例如:arr [ 0 ] ),该数组的大小可在程序运行过程中按照你的需求变动。


【百度百科】在 ANSI 的标准确立后,C语言的规范在一段时间内没有大的变动,

然而C++在自己的标准化创建过程中继续发展壮大。《标准修正案一》在1994年为C语言创建了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广的国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。被称为C99,C99被ANSI于2000年3月采用。

演示:

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

部分编译器可能会报错,可以试着将 a [ 0 ] 改为 a [ ]

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

6.1 柔性数组的特点:

1.结构中的柔性数组成员的前面必须至少有一个其他成员:

 
typedef struct st_type
{
    int i;//必须至少有一个其他成员
    int a[0];//柔性数组成员
}type_a;

2.sizeof 计算这种结构的大小是不包含柔性数组成员的:

 
#include <stdio.h>
struct S 
{
    int n; // 4
    int arr[]; // 大小是未知的
};
int main() 
{
    struct S s = { 0 };
    printf("%d\n", sizeof(s));//4
    return 0;
}

3.包含柔性数组成员的结构,用 malloc 函数进行内存分配,

并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小:

 
#include <stdio.h>
#include <stdlib.h>
struct S
{
    int n;
    int arr[0];
};
int main()
{
    //期望arr的大小是10个整型           给n的                 给arr[]的
    struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
    // 后面+的大小就是给柔性数组准备的
    return 0;
}

6.2 柔性数组的使用

代码1:使用柔性数组

 
#include <stdio.h>
#include <stdlib.h>
struct S
{
    int n;
    int arr[0];
};
int main()
{
    // 期望arr的大小是10个整型
    struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
    if (ps == NULL)
    {
        printf("malloc fail\n");
        return -1;
    }
    ps->n = 10;
    // 使用
    for (int i = 0; i < 10; i++)
    {
        ps->arr[i] = i;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    // 增容
    struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
    if (ptr != NULL)
    {
        ps = ptr;
    }
    // 再次使用 (略)
    // 释放
    free(ps);
    ps = NULL;
    return 0;
}

6.3 柔性数组的优势

代码2:直接使用指针

想让n拥有自己的空间,其实不使用柔性数组也可以实现。

 
#include <stdio.h>
#include <stdlib.h>
struct S 
{
    int n;
    int* arr;
};
int main() 
{
    struct S* ps = (struct S*)malloc(sizeof(struct S));
    if (ps == NULL)
    {
        printf("malloc fail\n");
        return -1;
    }
    ps->n = 10;
    ps->arr = (int*)malloc(10 * sizeof(int));
    if (ps->arr == NULL)
    {
        printf("malloc fail\n");
        return -1;
    }
    // 使用
    for (int i = 0; i < 10; i++)
    {
        ps->arr[i] = i;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    // 增容
    int* ptr = (struct S*)realloc(ps->arr, 20 * sizeof(int));
    if (ptr != NULL) 
    {
        ps->arr = ptr;
    }
    // 再次使用 (略)
    // 释放
    free(ps->arr); // 先free第二块空间
    ps->arr = NULL;
    free(ps);
    ps = NULL;
    return 0;
}

虽然 代码2 实现了相应的功能,但是和 代码1 比还是有很多不足之处的。代码2 使用指针完成,

进行了两次 malloc ,而两次 malloc 对应了两次 free ,相比于 代码1 更容易出错

上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

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

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体, 但是用户并不知道这个结构体内的成员也需要free,

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

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

连续内存多多少少有益于提高访问速度,还能减少内存碎片。malloc 的次数越多,
产生的内存碎片越多,这些内存碎片不大不小,
再次被利用的可能性很低。内存碎片越多,
内存的利用率就会降低。频繁的开辟空间效率会变低,碎片也会增加。
(其实也没多高了,反正跑不了要用做偏移量的加法来寻址)

内存碎片和内存池:

扩展阅读:

C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell


本篇完。

可以自己模拟实现:atoi + strncat + strncpy。

956bc1e8858a2dc88b70812934259a0e.gif

目录
相关文章
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
426 26
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
537 15
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
926 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
549 6
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
737 1
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
963 23
|
11月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1581 0
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
704 1
一文彻底搞清楚C语言的函数
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
922 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
441 24