C语言可变数组 嵌套的可变数组,翻过了山跨过了河 又掉进了坑

news2025/6/21 5:33:58

可变数组

专栏内容
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

在这里插入图片描述

概述

数组中元素是顺序存放,这一特性让我们存储和访问数据都很简单,
但也因为这一特性,我们在写代码时,往往不能确定数组元组的个数,只能按最大的数量进行预分配,
这不仅造成了空间浪费,而且使用起来不友好,明明我们要运行一个小数据集,但却要很多内存空间。

这就产生了可变数组,它的元素数量不需要在代码中确定,而是在运行时确定。

实现方式

可变数组在我们的程序中经常遇到,但是它有那些实现方式呢?
根据数组存储内存区域的不同,可以分为

  • 栈内存实现方式
  • 堆内存实现方式
    下面我们就来看看它们是如何实现,有什么不同

栈内存实现

这里C99中新增的VLA(variable-length array) 特性,可以让我们在用的时候定义数组,数组的长度不再是静态值,可以是变量中的值。
也就是说,数组的长度在程序编译阶段是不确定的,直到运行时再能确定,这就避够我们定义一个最大的数组,产生很多空间浪费。

  • 举例
void test(int n)
{
    /* check */
    if(n <= 0)
    {
        return;
    }

    // int arr[n] = {0};
    int arr[n];
    /* todo  */
    for(int i=0; i < n; i++)
    {
        arr[i] = i;
    }
    return;
}

数组arr的长度是变量n来确定

  • 注意事项
  1. 这个特性是C99引入,并不是所有的编译器都能完全支持,我使用的 gcc 版本是支持的。
[senllang@hatch toadbtest]$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/8/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --disable-libmpx --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 8.5.0 20210514 (Red Hat 8.5.0-19) (GCC)
  1. 使用VLA定义的数组,不能在定义时初始化,否则会产生以下错误,因为它不能使用默认的初始化器,必须由用户自己来初始化;
[senllang@hatch toadbtest]$ gcc test.c
test.c: In function ‘test’:
test.c:9:5: error: variable-sized object may not be initialized
     int arr[n] = {0};
     ^~~
test.c:9:19: warning: excess elements in array initializer
     int arr[n] = {0};

堆内存实现

在用的时候,通过malloc动态申请数组空间,空间大小为 数组元素类型的n倍,n是我们需要的数组大小,它可以是输入,也可以是程序运行过程中的可变值。

这种方式是我们普遍使用的,也是所有编译器都支持的。

  • 举例
void test(int n)
{
    int *arr = NULL;

    /* check */
    if(n <= 0)
    {
        return;
    }

    arr = (int *)malloc(sizeof(int)*n);
    if(NULL == arr)
    {
        return ;
    }

    /* todo  */
    for(int i=0; i < n; i++)
    {
        arr[i] = i;
    }
    return;
}

访问方式

数组访问一般有指针方式和下标方式,这与普通数组没有什么区别,为什么要谈数组的访问方式呢? 因为这里会隐藏着惊天大坑,我们接着往下看。

C语言里一般,数组可以转成指针,当然指针也可以转成数组来用。

数组下标访问

这就很简单了,数组中的元素都是顺序排列,那么按它们的位置序号访问就可以。

对于VLA方式定义,还是动态申请方式分配的空间,它们的元素存储的内存空间都是连续的,所以两种方式下都可以用下标的方式来访问。

  • 对于数组,那就再正常不过了,递增下标就可以获取到各元素的值;
  • 而对于动态申请的数组,本身就是指向内存空间的首地址,也可以理解为指向数组的指针,即常说的数组指针,用下标的方式就可以直接获取到对应的元素值。
/* 如上面举例,指针类型定义的数组,也可以下标进行访问 */
int *arr = NULL;
arr[i] = i;

指针访问

指针形式访问,每次指针的移动步长,都是指针基础类型的字节数;
此时取值时,就要以指针的方式来取值;

对于VLA方式定义,还是动态申请方式分配的空间,它们的元素存储的内存空间都是连续的,所以两种方式下都可以用指针的方式来访问。

  • 对于数组,数组名就是首个元素的地址,遍历时每次递增+1,就会移动到下一个元素的地址;
  • 而对于动态申请的数组,本身就是指向内存空间的首地址,也是0号元素的首地址;
int testarr[n];
int *arr = testarr;

for(int i = 0; i < n; i++,arr++)
{
    *arr = i;
}

此处专门定义一个数组,然后将数组首地址赋给指针,用指针来访问数组元素

可变数组的嵌套使用

如果一个结构体里含有可变数组,同时结构体又存在嵌套,看起来都有点复杂,那它如何分配空间和访问呢?

定义

假如我们定义如下结构体,最终我们使用的是 stGroupData 这个结构体;

typedef struct Postion
{
    int x;
    int y;
}stPosition, *pstPostion;

typedef struct MemberData
{
    int posCnt;
    stPosition posArr[];
}stMemberData, *pstMemberData;

typedef struct GroupData
{
    int group_id;
    int memberCnt;
    stMemberData memberData[];
}stGroupData, *pstGroupData;

大家是否好奇,上面结构的大小时多少呢?这个留给大家一个作业,知道答案的同学可以在评论区给出来。

分配空间

因为存在嵌套,所以就不能用VLA这个特性了,只能用动态分配了。
动态分配时,需要对外层结构体和内层结构体的元素分别计算,这里很容易遗漏;

假设我们有一组数据,需要2个memberdata:

memberdata 0: 有3个postion
memberdata 1: 有1个postion

坑一:占用空间

空间需要分配多少呢?

  • 可能初看好像是sizeof(stGroupData) 就可以了;
  • 再看,其实需要 sizeof(stGroupData) + 2*sizeof(stMemberData) 大小;

这就掉坑里了。下面是正确的大小计算;

计算空间大小

int size = 0;
pstGroupData pgData = NULL;

/* 计算一个要分配的空间大小,假设2个memberdata:
 * memberdata 0: 有3个postion
 * memberdata 1: 有1个postion 
 */
size = sizeof(stGroupData) + 2*sizeof(stMemberData) + 4 * sizeof(stPosition);
pgData = (pstGroupData)malloc(size);

这里计算size时,先计算结构体头部的size,因为数组部分没有定义长度,sizeof 出的来的值是不包含的,所以需要单独计算;
外层stGroupData中包含两个元素, 内层 stMemberData中分别为 3和1,也就是4个元素空间 ,再加上外层的结构体大小,就是整个所占的内存空间。
它们的内存空间分布情况,假设首地址从0开始

在这里插入图片描述

访问数组

那么按上面的例子,定义了一个结构体,如何访问各个数组元素呢?
可能有小伙伴立刻就想到了下标的方式 ,那么我们来看一下

坑二:下标访问

此时我们用下标方式引用会是正确的吗?

pgData->memberData[0] 
pgData->memberData[1] 

memberData[0] 与 memberData[1]的地址相差,应该是一个元素的sizeof(stMemberData) = 4,也就是一个int posCnt空间大小;
从内存分布图来看,就会变成这样

在这里插入图片描述

嵌套可变数组的访问

此时下标访问是不对的,不能采用默认的类型大小进行移动;
只能用指针方式来访问,同时需要自己计算下一个元素的偏移大小

pstMemberData pmData = NULL;

/* memberData[0] */
pmData = pgData->memberData;

/* memberData[1] */
pmData = (pstMemberData)((char*)(pgData->memberData) + sizeof(stMemberData) + 3 * sizeof(stPosition));

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/862033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java获取路径时Class.getResource()和ClassLoader.getResource()区别

Java中取资源时&#xff0c;经常用到Class.getResource()和ClassLoader.getResource()&#xff0c;Class.getResourceAsStream()和ClassLoader().getResourceAsStream()&#xff0c;这里来看看他们在取资源文件时候的路径有什么区别的问题。 环境信息&#xff1a; 系统&#…

css3瀑布流布局遇见截断下一列展示后半截现象

css3 瀑布流布局遇见截断下一列展示后半截现象 注&#xff1a;css3实现瀑布流布局简直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 场景-在uniapp项目中 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px&#xff0c;column-count:2,2列展示…

基于k8s的devOps自动化运维平台架构设计(中英文版本)

▲ 点击上方"DevOps和k8s全栈技术"关注公众号 In the rapidly evolving landscape of software development and IT operations, DevOps has emerged as a transformative approach to bridge the gap between development and operations teams. One of the key ena…

第五期(2022-2023)传统行业云原生技术落地调研报告——央国企篇

随着国务院国资委印发《关于加快推进国有企业数字化转型工作的通知》&#xff0c;开启了国有企业数字化转型的新篇章。大型央、 国企纷纷顺应趋势&#xff0c;加速云化布局&#xff0c;将数字化转型工作定位为“十四五”时期重点任务。同时&#xff0c;越来越多的企业通过云原生…

【Leetcode】155. 最小栈、JZ31 栈的压入、弹出序列

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 155. 最小栈 155. 最小栈 题目描述; 设计一个支持 push &#xff0c;pop &#xff0c;top …

C语言笔记7

#include <stdio.h> int main(void) {int a123;int b052;//十进制42int c0xa2;//十进制162printf("a%d b%o c%x \n",a,b,c);//分别是十进制 八进制 十六进制printf("a%d b%d c%d \n",a,b,c);printf("Hello 凌迟老头\n");return …

uniapp 使用canvas画海报(微信小程序)

效果展示&#xff1a; 项目要求&#xff1a;点击分享绘制海报&#xff0c;并实现分享到好友&#xff0c;朋友圈&#xff0c;并保存 先实现绘制海报 <view class"data_item" v-for"(item,index) in dataList" :key"index"click"goDet…

并发——线程池,Executor 框架

文章目录 1 简介2 Executor 框架结构(主要由三大部分组成)1) 任务(Runnable /Callable)2) 任务的执行(Executor)3) 异步计算的结果(Future) 3 Executor 框架的使用示意图 1 简介 Executor 框架是 Java5 之后引进的&#xff0c;在 Java 5 之后&#xff0c;通过 Executor 来启动…

vue+springboot基于web的火车高铁铁路订票管理系统

铁路订票管理系统按照权限的类型进行划分&#xff0c;分为用户和管理员两个模块。管理员模块主要针对整个系统的管理进行设计&#xff0c;提高了管理的效率和标准。主要功能包括个人中心、用户管理、火车类型管理、火车信息管理、车票预订管理、车票退票管理、系统管理等&#…

解决遥感技术在生态、能源、大气等领域的碳排放监测及模拟问题

以全球变暖为主要特征的气候变化已成为全球性环境问题&#xff0c;对全球可持续发展带来严峻挑战。2015年多国在《巴黎协定》上明确提出缔约方应尽快实现碳达峰和碳中和目标。2019年第49届 IPCC全会明确增加了基于卫星遥感的排放清单校验方法。随着碳中和目标以及全球碳盘点的现…

单源最短路

无负环 Dijkstra 迪杰斯特拉算法 采用的贪心的策略 每次遍历到始点距离最近且未访问过的顶点的邻接节点&#xff0c;直到扩展到终点为止 Dijkstra求最短路 I 给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正值。 请你求出 1 …

微服务 云原生:基于 Gogs + Drone 实现 CI/CD 自动化

一般构建部署 以一个简单的前后端项目来说&#xff0c;分别编写前后端的 Dockerfile 文件并构建镜像&#xff0c;然后编写 docker-compose.yml 构建部署&#xff0c;启动运行。每次代码变更后都需重新手动打包、构建、推送。 一个简单的例子&#xff1a; 前端&#xff1a; 项…

解读HTML-入门第一文

HTML详细解读 概念解读基本结构常用标签标题标签&#xff08;h1~h6&#xff09;段落标签&#xff08;p&#xff09;链接标签&#xff08;a&#xff09;图像标签&#xff08;img&#xff09;列表标签&#xff08;ul、ol、li&#xff09;表格标签&#xff08;table、tr、td&#…

轻量级锁实现1——结构体解析、初始化

瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;14 文档用途 从底层理解轻量级锁的实现&#xff0c;从保护共享内存的角度理解轻量级锁的使用场景&#xff0c;包括上锁、等待、释放&#xff0c;理…

android 如何分析应用的内存(十六)——使用AS查看Android堆

android 如何分析应用的内存&#xff08;十六&#xff09;——使用AS查看Android堆 在前面&#xff0c;先介绍了如何使用jdb和VS code查看应用栈相关内容。 本文将介绍&#xff0c;如何查看堆中的内容。大概有&#xff1a; 堆中的对象&#xff0c;有哪些堆中的对象&#xff0…

“Can‘t open perl script configure : No such file or directory”的解决办法

编译OpenSSL的时候执行到 perl configure 时提示找不到configure&#xff0c; 然后在网上搜了搜&#xff0c;大家给的解决办法一般都是说设置环境变量或者指定configure路径再执行&#xff1b;我试了都不行&#xff0c; 最后我把perl卸了重装就正常了&#xff1b; 然后我换了…

QEMU源码全解析32 —— Machine(2)

接前一篇文章&#xff1a;QEMU源码全解析31 —— Machine&#xff08;1&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 上一篇文章给m…

【力扣每日一题】2023.8.11 矩阵对角线元素的和

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个矩阵&#xff0c;让我们把矩阵对角线上的元素都加起来返回。 那么矩阵的对角线是有两条的&#xff0c;一条是从左上到右下…

Maven安装与配置教程

目录 一、前言 1.什么是Maven 2.为什么要使用Maven 二、Maven安装与配置 1.官网下载 2.Maven配置 3.修改Maven仓库下载镜像及修改仓库位置 3.1.修改仓库下载镜像地址 3.2.修改默认Maven的仓库位置 三、eclipse配置Maven 四、eclipse部署Maven项目 注意事项&#xff…

Python非线性全局优化

文章目录 全局优化函数简介详解性能测试 全局优化函数简介 scipy的optimize模块非常强大&#xff0c;也是我个人使用最多的scipy模块&#xff0c;这里面封装的都是成熟且高效的算法&#xff0c;久经考验。对于参加数学竞赛的同学来说&#xff0c;辛辛苦苦撸出来的遗传算法、模…