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

可变数组

专栏内容
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 initializedint arr[n] = {0};^~~
test.c:9:19: warning: excess elements in array initializerint 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.mzph.cn/news/35852.shtml

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

相关文章

Mysql 搭建MHA高可用架构,实现自动failover,完成主从切换

目录 自动failover MHA&#xff1a; MHA 服务 项目&#xff1a;搭建Mysql主从复制、MHA高可用架构 实验项目IP地址配置&#xff1a; MHA下载地址 项目步骤&#xff1a; 一、修改主机名 二、编写一键安装mha node脚本和一键安装mha mangaer脚本&#xff0c;并执行安装 …

你真的了解ORM吗?通过一个简单的例子来学习ORM

什么是ORM ORM&#xff08;Object-Relational Mapping&#xff09;是一种将面向对象程序数据模型与关系数据库之间进行映射的技术。 比如数据库表user&#xff0c;它有id、name、age字段映射到Java实体类就是User类&#xff0c;有id、name、age属性。 CREATE TABLE user (id…

2023国赛 高教社杯数学建模ABCDE题思路汇总分析

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 全国大学生数学建模…

Design-Pattern设计模式

Design-Pattern设计模式 图说设计模式 图说设计模式 在线书籍 软件模式是将模式的一般概念应用于软件开发领域&#xff0c;即软件开发的 总体指导思路或参照样板。软件模式并非仅限于设计模式&#xff0c;还包括 架构模式、分析模式和过程模式等&#xff0c;实际上&#xff…

FFmpeg常见命令行(四):FFmpeg流媒体

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

UML 类图的画法

1.类图的画法 类 整体是个矩形&#xff0c;第一层类名&#xff0c;第二层属性&#xff0c;第三层方法。 &#xff1a;public- : private# : protected空格: 默认的default 对应的类写法。 public class Student {public String name;public Integer age;protected I…

【hello C++】特殊类设计

目录 一、设计一个类&#xff0c;不能被拷贝 二、设计一个类&#xff0c;只能在堆上创建对象 三、设计一个类&#xff0c;只能在栈上创建对象 四、请设计一个类&#xff0c;不能被继承 五、请设计一个类&#xff0c;只能创建一个对象(单例模式) C&#x1f337; 一、设计一个类&…

Sentinel使用实例

不说了&#xff0c;直接上官方文档 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md Sentinel Example 项目说明 本项目演示如何使用 Sentinel starter 完成 Spring Clo…

【金融量化】对企业进行估值的方法有哪些?

估值的方法有哪些&#xff1f; 如何对企业进行估值&#xff1f;有2个方法估算。 1 绝对估值法 它是一种定价模型&#xff0c;用于计算企业的内在价值。 比如说你可以根据公司近N年的现金流情况。借此去预测未来N年的现金流情况。所有的现金流数据都可以在年报上查询到。最后…

ios 知识

IOS 类文件.h和.m中interface的区别 大家都知道我们在创建类文件时会发现&#xff1a; #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend和 #import "ViewController.h"interface ViewController ()end那么他们之间有何区别呢&#x…

力扣:62. 不同路径(Python3)

题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

WebRTC音视频通话-WebRTC本地视频通话使用ossrs服务搭建

iOS开发-ossrs服务WebRTC本地视频通话服务搭建 之前开发中使用到了ossrs&#xff0c;这里记录一下ossrs支持的WebRTC本地服务搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c;支持RTM…

STM32CubeIDE的安装和黑色主题及自动补全代码

STM32CubeIDE之前用过一点时间&#xff0c;但后来因为不习惯放弃了最近在新电脑上又用起来了&#xff0c;感觉相对之前好了很多&#xff0c;其实如果在工作中基本使用的是STM32,用意法的生态软件也挺好的&#xff0c;意法最近在这块也在大力发展&#xff0c;STM32CubeIDE安装包…

【Linux】云服务器自动化部署VuePress博客(Jenkins)

前言 博主此前是将博客部署在 Github Pages&#xff08;基于 Github Action&#xff09;和 Vercel 上的&#xff0c;但是这两种部署方式对于国内用户很不友好&#xff0c;访问速度堪忧。因此将博客迁移到自己的云服务器上&#xff0c;并且基于 Jenkins&#xff08;一款开源持续…

浪涌保护器中SPD防雷模块的主要应用方案

浪涌保护器&#xff08;Surge Protective Device&#xff0c;SPD&#xff09;是一种用于限制瞬态过电压和导引泄放电涌电流的非线性防护器件&#xff0c;用以保护耐压水平低的电器或电子系统免遭雷击及雷击电磁脉冲或操作过电压的损害。SPD可以将过电压泄放到地线或限制过电压到…

多元最短路(Floyd)

是一个基于动态规划的全源最短路算法。它可以高效地求出图上任意两点之间的最短路 时间复杂度 O(n^3) 状态转移方程 f[i][j]min(f[i][j],f[i][k]f[k][j]) 核心代码 void floyd(){for(int k1;k<n;k)for(int i1;i<n;i)for(int j1;j<n;j)s[i][j]min(s[i][j],s[i][k…

Vue前端 更具router.js 中的meta的roles实现路由卫士,实现权限判断。

参考了之篇文章 1、我在登陆时获取到登录用户的角色名roles&#xff0c;并存入sessionStorage中&#xff0c;具体是在login页面实现&#xff0c;还是在menu页面实现都可以。在menu页面实现&#xff0c;可以显得登陆快一些。 2、编写router.js&#xff0c;注意&#xff0c;一个…

Spring 事务详解

目录 一、概述二、事务的特性&#xff08;ACID&#xff09;三、Spring 的事务管理3.1 编程式事务管理3.2 编程式事务管理 四、Spring 事务管理接口及其定义的属性4.1 PlatformTransactionManager:事务管理接口4.2 TransactionDefinition:事务属性4.3 TransactionStatus:事务状态…

Stable Diffusion+Temporal-kit 半虚半实应用

1.先下载temporal-kit,重启webui 2.下载好ffmpeg,配置好环境,下载Ebsynth 3.准备好你需要的视频,拖到预处理视频位置 4.填写参数,点解保存设置,然后并点击生成,会生成到目标文件夹的input位置 5.然后拉出input文件夹里面你想切换成处理的帧图片,然后填写prompt查看效…

中国省级、城市-数字经济创新创业、分项指数(2010-2020年)

一、数据介绍 数据名称&#xff1a;中国省级、城市-数字经济创新创业、分项指数 数据年份&#xff1a;2010-2020年 数据范围&#xff1a;31省、336个城市 数据来源&#xff1a;北大企业大数据研究中心 二、参考文献 参考文献&#xff1a; 戴若尘,王艾昭,陈斌开.中国数字…