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代码时,了解两种内存管理方式的差异非常重要。
454 26
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
558 15
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
348 35
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
705 6
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3541 0
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1197 1
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
1684 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
1208 0

热门文章

最新文章