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

目录
相关文章
|
2月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
908 0
|
4月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
175 26
|
4月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
301 15
|
9月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
309 1
一文彻底搞清楚C语言的函数
|
4月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
1440 0
|
4月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
415 1
|
4月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
400 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
895 0
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。

热门文章

最新文章