当前位置: 首页 > news >正文

零长数组,whatwhy

  • GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展

1 what

struct Packet
{int state;int len;char arr[0]; // 这里的0长结构体就为变长结构体提供了非常好的支持
};
  • 用途:定义数据结构时非常有用,满足需要变长度的结构体,创建结构体对象时,可根据实际的需要指定这个可变
    长数组的长度,并分配相应的空
  • 用法如上:声明一个长度为0的数组,是的结构体是可变长的,对于编译器来说,长度为0的数组不占用空间,只是一个偏移量,数组名代表了一个不可修改的地址常量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct buffer
{int len;char a[0];
};int main(void)
{struct buffer *buf;buf = (struct buffer *)malloc(sizeof(struct buffer) + 20);buf->len = 20;strcpy(buf->a, "hello\\n"); // 再加一个\表示不是转义字符puts(buf->a);               //标准设备输出 hello\n// puts 函数输出字符串自动换行输出free(buf);return 0;
}
  • 我们使用 malloc 申请一片内存,大小为 sizeof(buffer) + 20,即24个字节大小。其中4个字节用来存储结构体指针 buf 指向的结构体类型变量,另外20个字节空间,才是我们真正使用的内存空间。我们可以通过结构体成员 a,直接访问这片内存
  • 通过这种灵活的动态内存申请方式,这个 buffer 结构体表示的一片内存缓冲区,就可以随时调整,可大可小。这个特性,在一些场合非常有用。
  • 比如,现在很多在线视频网站,都支持多种格式的视频播放:标清、高清、超清、蓝光甚至4K。如果我们本地程序需要在内存中申请一个 buffer 用来缓存解码后的视频数据,那么,不同的播放格式,需要的 buffer 大小是不一样的。 如果我们按照 4K 的标准去申请内存,那么当播放标清视频时,就用不了这么大的缓冲区,白白浪费内存。 而使用变长结构体,我们就可以根据用户的播放格式设置,灵活地申请不同大小的 buffer,大大节省了内存空间

2 why

问什么不用指针代替零长数组 ?
数组名在作为函数参数传递时,确实传递的是一个地址,但数组名绝不是指针,两者不是同一个东西。数组名用来表征一块连续内存存储空间的地址,是个常量,表示一个地址,而指针是一个变量,编译器要给它单独再分配一个内存空间,用来存放它指向的变量的地址

#include <stdio.h>struct buffer1
{int len;int a[0];
};
struct buffer2
{int len;int *a;
} __attribute__((packed)); // 将结构体压实int main(void)
{printf("buffer1: %ld\n", sizeof(struct buffer1)); // buffer1: 4printf("buffer2: %ld\n", sizeof(struct buffer2)); // buffer2: 12return 0;
}
  • 数组名在作为函数参数传递时,确实传递的是一个地址,但数组名绝不是指针,两者不是同一个东西。数组名用来表征一块连续内存存储空间的地址,而指针是一个变量,编译器要给它单独再分配一个内存空间,用来存放它指向的变量的地址
  • 而数组名,编译器不会再给其分配一个存储空间的,它仅仅是一个符号,跟函数名一样,用来表示一个地址
而数组名,编译器不会再给其分配一个存储空间的#include <stdio.h>
#include <stdlib.h>typedef struct student
{int num;       // 学号char name[20]; // 姓名char sex;      // 性别
} student;typedef struct class
{char *teacher;int class_id;student students[];
} class;int main()
{class *classA;classA = (class *)malloc(sizeof(class) + sizeof(student) * 5);// 遍历学生赋值for (int i = 0; i < 5; i++){classA->students[i].num = 230580 + i;printf("请输入学号为%d的姓名:\n", classA->students[i].num);scanf("%s", classA->students[i].name);}// 遍历学生输出for (int i = 0; i < 5; i++){printf("学号%d的姓名是%s:\n", classA->students[i].num, classA->students[i].name);}// 释放资源free(classA);classA = NULL; // 置空return 0;
}

用指针来写

#include <stdio.h>
#include <stdlib.h>typedef struct student
{int num;       // 学号char name[20]; // 姓名char sex;      // 性别
} student;typedef struct class
{char *teacher;int class_id;student *students;
} class;int main()
{class *classA;classA = (class *)malloc(sizeof(class));classA->students = (student *)malloc(sizeof(student) * 5);// 遍历学生赋值for (int i = 0; i < 5; i++){classA->students[i].num = 230580 + i;printf("请输入学号为%d的姓名:\n", classA->students[i].num);scanf("%s", classA->students[i].name);}// 遍历学生输出for (int i = 0; i < 5; i++){printf("学号%d的姓名是%s:\n", classA->students[i].num, classA->students[i].name);}// 释放资源free(classA->students);classA->students = NULL;free(classA);classA = NULL;return 0;
}
  • 零长数组一般和结构体搭配使用,其比起在结构体中声明一个指针变量、再进行动态分配的效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。
  • 同时因为零长数组表示数据内容时,其数据空间是动态分配的,所以比静态分配内存要灵活。

相关文章:

  • Java面试宝典
  • JS-11A/11时间继电器 板前接线 JOSEF约瑟
  • 详细介绍如何利用 A star(A*)算法解决8数码问题
  • 基于Java,SSM,html,Vue在线视频播放管理系统网站设计
  • Windows通过git配置github代码仓库全流程
  • Android compose 使用指纹验证
  • GDAL升级到3.0之后遇到的坑
  • MySQL与SQLite区别
  • 【Frida】【Android】 07_爬虫之网络通信库HttpURLConnection
  • 【并发编程】CountDownLatch
  • 多线程中常用的一些方法介绍
  • Mongodb中一个小巧的数据更新命令$inc
  • Arraylist,TreeSet,TreeMap的增删改查及遍历
  • 自我认识的方法模型图
  • 二维码:技术、商业与未来
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • Django 博客开发教程 16 - 统计文章阅读量
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Java比较器对数组,集合排序
  • jdbc就是这么简单
  • scala基础语法(二)
  • SpingCloudBus整合RabbitMQ
  • Theano - 导数
  • Vim Clutch | 面向脚踏板编程……
  • webpack+react项目初体验——记录我的webpack环境配置
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 聚类分析——Kmeans
  •  一套莫尔斯电报听写、翻译系统
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 正则表达式
  • raise 与 raise ... from 的区别
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #{}和${}的区别?
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (0)Nginx 功能特性
  • (1)(1.9) MSP (version 4.2)
  • (poj1.2.1)1970(筛选法模拟)
  • (poj1.3.2)1791(构造法模拟)
  • (二)Eureka服务搭建,服务注册,服务发现
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (转)创业的注意事项
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .net 按比例显示图片的缩略图
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [Android View] 可绘制形状 (Shape Xml)
  • [Android]使用Retrofit进行网络请求