【维生素C语言】第十五章 - 柔性数组(可变长数组)

简介: 本篇将对C99标准中引入的新特性——柔性数组,进行讲解。并探讨柔性数组的优势,简单的介绍内存池的相关概念,来体会柔性数组的优点。

前言


本篇将对C99标准中引入的新特性——柔性数组,进行讲解。并探讨柔性数组的优势,简单的介绍内存池的相关概念,来体会柔性数组的优点。


一、柔性数组介绍


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


🔍 出处:柔性数组(Flexible Array),是在C语言的 C99 标准中,引入的新特性。结构中的最后一个元素的大小允许是未知的数组,即为柔性数组。


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


💬 演示:


struct S {
    int n;
    int arr[]; // 👈 柔性数组成员
};
 ❗  部分编译器可能会报错,可以试着将 a [ 0 ] 改为 a [ ] :
struct S {
    int n;
    int arr[]; // 👈 柔性数组成员
};

二、柔性数组的特点


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


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

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


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

🚩  4


💬 包含柔性数组成员的结构,用 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) + sizeof(int)); 
    // 后面+的大小就是给柔性数组准备的
    return 0;
}


💡 分析:

724c53b699a1ac9b9f00978d7f569ea6_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


三、柔性数组的使用


💬 代码演示:


#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) + sizeof(int));
    ps->n = 10;
    // 使用
    int i = 0;
    for (i = 0; i < 10; i++) {
        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;
}


🐞 查看地址:

3eda93c23fef59192872e866c75b9a79_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


四、柔性数组的优势


💬 代码1:使用柔性数组


/* 代码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));
    ps->n = 10;
    // 使用
    int i = 0;
    for (i = 0; i < 10; i++) {
        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;
}


💬 代码2:直接使用指针


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


/* 代码2 */
#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)
        return 1;
    ps->n = 10;
    ps->arr = (int*)malloc(10 * sizeof(int));
    if (ps->arr == NULL)
        return 1;
    // 使用
    int i = 0;
    for (i = 0; i < 10; i++) {
        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;
}


❓ 上面的 代码1 和 代码2 可以完成同样的同能,哪个更好呢?


💡 显而易见, 代码1 更好:


     ① 第一个好处:有利于内存释放


虽然 代码2 实现了相应的功能,但是和 代码1 比还是有很多不足之处的。代码2 使用指针完成,进行了两次 malloc ,而两次 malloc 对应了两次 free ,相比于 代码1 更容易出错如果我们的代码是在一个给别人用的函数中,你在里面做了两次内存分配,并把整个结构体返回给用户。虽然用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free,所以你不能指望用户来发现这件事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好(而不是多次分配),并且返回给用户一个结构体指针,用户只需使用一次 free 就可以把所有的内存都给释放掉,可以间接地减少内存泄露的可能性。


     ② 第二个好处:有利于访问速度


连续内存多多少少有益于提高访问速度,还能减少内存碎片。malloc 的次数越多,产生的内存碎片就越多,这些内存碎片不大不小,再次被利用的可能性很低。内存碎片越多,内存的利用率就会降低。频繁的开辟空间效率会变低,碎片也会增加。

db3827592ddde08317a3942f164acab0_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

🔺 总结:因此,使用柔性数组,多多少少是有好处的。



相关文章
|
存储 人工智能 程序员
一文彻底搞明白C语言的数组
本文详细介绍了C语言中的数组,包括定义、初始化(静态与动态)、存储方式、访问方法及常用操作,如遍历、修改元素和作为函数参数传递。数组是C语言中最基本的数据结构之一,掌握它对编程至关重要。下篇将介绍二维数组,敬请期待!
915 0
一文彻底搞明白C语言的数组
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
1217 6
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
605 5
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
461 4
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
算法 编译器 程序员
C语言学习笔记—P11(数组<2>+图解+题例+三子棋游戏<初级>)
C语言学习笔记(数组<2>+图解+题例+三子棋游戏<初级>)
309 0
C语言学习笔记—P11(数组<2>+图解+题例+三子棋游戏<初级>)
|
存储 C语言
C语言学习笔记—P10(数组<1>+图解+题例)
C语言学习笔记(数组<1>+图解+题例)
372 0
C语言学习笔记—P10(数组<1>+图解+题例)
|
C语言
C语言学习笔记——数组(二)
C语言学习笔记——数组
480 0
C语言学习笔记——数组(二)