数据结构——结构体 内存对齐

news2024/11/20 8:27:45

C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。结构体的定义形式为:

struct 结构体名{
    结构体所包含的变量或数组
};

1. 定义结构体变量

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。结构体变量才包含了实实在在的数据,需要内存空间来存储。

  • 方式一:先定义结构体,再定义结构体变量

//定义stu结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

//定义两个结构体变量
struct stu stu1, stu2;

stu为结构体名,里面包含name、num、age、group、score这5个成员。stu1stu2则为两个stu类型的结构体变量。

  • 方式二:在定义结构体的同时定义结构体变量

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1 stu2;

直接将变量放在结构体的最后即可。

如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。

  • 注意:在结构体内部定义结构体

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    struct sub1{
        char group;  //所在学习小组
    } sub1;
    struct sub2{
        float score;  //成绩
    };
};

如上所示,在stu结构体里还定义了『结构体变量sub1』和『结构体sub2』,由于sub2没有定义变量,所以其内部成员score即为母结构体stu的成员变量。

2. 成员的获取和赋值

使用点号.获取结构体变量的单个成员,然后再进行赋值操作。

//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.group = 'A';
stu1.score = 135;

//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);

//打印结果
Tom的学号是12,年龄是18,在A组,今年的成绩是135!

也可以在定义结构体变量时整体赋值:

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };

3. 结构体的内存分配

结构体中各成员在内存中是按顺序依次存储的,成员之间不互相影响,各占用不同的内存空间。结构体变量占用的内存大于等于所有成员占用的内存的总和,因为成员在存储时需要遵循结构体的内存对齐规则,成员之间可能会存在裂缝。

 

结构体内存对齐

4. 计算结构体的内存大小

先来看看结构体的内存对齐规则:

1. 数据成员对⻬规则:结构体的第⼀个数据成员会存放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组, 结构体等)的整数倍开始存储(⽐如int为4字节,则要从4的整数倍地址开始存储)。

2. 结构体作为成员:如果⼀个结构里有某些结构体成员,则结构体成员要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。(struct a⾥存有struct b,b⾥有char, int, double等元素,那b应该从8的整数倍开始存储.)

3. 总内存对齐:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员所占内存大小的整数倍,不⾜的要补⻬。若结构体a里包含结构体成员b,则需要将a的其他成员和b里的成员相比,得到最大成员内存,再按照最大内存的倍数进行补齐。

看完内存对齐规则是不是感觉有点绕?不急,接下来通过分析具体例子来理解这个规则。

示例1:含有多种数据类型成员

struct Struct {
    double a;   //8 (0-7)
    char b;     //1 [8 1] (8)
    int c;      //4 [9 4] 9 10 11 (12 13 14 15)
    short d;    //2 [16 2] (16 17)
} struct1;

//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));

//输出结果
struct1 = 24    

输出结果分析:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • char b:b只占用1个字节内存,会按顺序存放在位置8处。
  • int c:c占用4字节内存,本该从位置9开始存放,但根据规则1可知,c必须从4的整数倍开始存储,而9不是4的整数倍,需要后移到位置12才开始存储,所以c存放在结构体内存中的12-15的位置。
  • short d:d占用2字节内存,可以接着从位置16开始存储,所以存放在16-17的位置。

根据上面的分析可知,struct1的成员总共需要18字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为24。

sizeof:是一个运算符,用来计算传进来的数据类型占用多大的内存,在编译时即可完成运算。比如:sizeof(int)为4字节,sizeof(double)为8字节。

示例2:交换成员位置

struct Struct {
    double a;   //8 (0-7)
    int c;      //4 (8 9 10 11)
    char b;     //1 (12)
    short d;    //2 [13 2] (14 15) - 16
} struct2;

//输出内存大小
printf("struct2 = %lu \n", sizeof(struct2));

//输出结果
struct2 = 24    

这次在示例1中struct的基础上交换了成员b和c的位置,输出结果就不一样了,分析如下:

  • double a:a占用8字节内存,作为第一个成员,a会在结构体所在内存中的0-7的位置存放。
  • int c:c占用4字节内存,可以接着从位置8开始存储,所以c存放在结构体内存中的8-11的位置。
  • char b:b只占用1个字节内存,可以直接存放在位置12处。
  • short d:d占用2字节内存,本该从位置13开始存放,根据规则1可知,由于13不是2的倍数,需要后移到位置14开始存储,所以存放在14-15的位置。

根据上面的分析可知,struct2的成员总共需要16字节内存,根据规则3,struct2的内存大小必须是8(double a)的整数倍,所以最后内存大小为16。

示例3:结构体嵌套结构体

直接在struct1里加上一个struct2成员,然后输出内存大小

//在最后加上成员e
struct Struct1 {
    double a;   // 8
    char b;     // 1
    int c;      // 4
    short d;    // 2
    struct Struct2 e;  //16
} struct1;

//输出内存大小
printf("struct1 = %lu \n", sizeof(struct1));

//输出结果
struct1 = 40

从之前struct1的分析可知,a、b、c、d实际占用18字节(位置0-17),那成员e就需要从位置18开始存放。由于e是个结构体,根据规则2,当结构体作为成员时,需要从其内部最⼤元素所占内存⼤⼩的整数倍地址开始存储。结构体e中内存占用最大的元素是double a,为8字节,所以e就需要从8的整数倍地址开始存储,即后移到位置24开始存储,e本身占用16字节内存,所以存放位置是24-39。

根据上面的分析可知,struct1的成员总共需要40字节内存,根据规则3,struct1的内存大小必须是8(double a)的整数倍,所以最后内存大小为40。

对于这个示例,大家可以改变成员e在Struct1中的位置,或者更改struct1和e里的成员类型,再看下输出结果和自己计算的结果是否相同。



作者:东篱采桑人
链接:https://www.jianshu.com/p/60b64935b0a5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

相关文章

试用「ChatGPT」几周之后

冷静下来,不吹不黑。 01 最近半年,互联网一款现象级的应用诞生:「ChatGPT」; 其火爆的程度,不输前面的羊了个羊; 最初了解到ChatGPT还是春节的时候,但那时网上的测评还没引起足够的好奇心&…

美女诱惑来袭,你抖的过嘛~python下载

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 我又又又来采集美女小姐姐了 兜兜转转还是小姐姐得我心吖~ 哈哈哈哈哈哈哈哈哈哈 目录&#xff08;想看哪里点哪里 &#x1f61d;&#xff09; 前言开发环境:思路流程: <固定公式>代码展示尾语 开发环境: 首先我…

kudu可视化工具:kudu-plus

目录 kudu kudu-plus是什么 kudu基础 分支说明 kudu-plus版本功能实现 kudu Kudu是为Apache Hadoop平台开发的列式数据库。Kudu拥有Hadoop生态系统应用程序的常见技术属性&#xff1a;它可以商用硬件上运行&#xff0c;可横向扩展&#xff0c;并支持高可用性操作。 kudu-p…

并发编程之循环屏障CyclicBarrier

文章目录 前言什么是CyclicBarrierCyclicBarrier原理CyclicBarrier VS CountDownLatchCountDownLatch图示&#xff1a;CyclicBarrier图示&#xff1a;两者的异同&#xff1a; CyclicBarrier核心源码实战演示1、创建测试demo2、创建测试用例3、查看测试结果 写在最后 前言 前面…

Ubuntu 20.04安装mysql8并配置远程访问

文章目录 一、使用apt-get安装mysql服务二、初始化mysql数据库管理员用户密码三、配置远程访问 一、使用apt-get安装mysql服务 # 更新软件源 apt-get install update# 安装mysql服务 apt-get install mysql-server# 使用mysqladmin工具查看mysql版本 mysqladmin --version# 启…

powershell定义文本,用户交互,正则表达式

定义文本 PS C:\Users\Administrator> $site"yuan" PS C:\Users\Administrator> $text"$site $(get-date) $env:windir" PS C:\Users\Administrator> $text yuan 09/16/2022 14:12:26 C:\Windows#使用单引号闭合字符串输出双引号 The site of my…

【Jeston Orin】Orin nano 8G模块使用官方系统包生成标准烧写系统测试

大家好&#xff0c;我是虎哥&#xff0c;GTC 2023上&#xff0c;NVIDIA正式推出了面向边缘AI的新一代入门款开发套件&#xff0c;Jetson Orin Nano Developer Kit。虽说只是入门套件&#xff0c;但据说相比上一代Jetson Nano有最高达80倍的性能提升&#xff01;于是我在收到包裹…

苹果ipad触控笔哪个好?平价电容笔排行榜

因为ipad本身的性能足够强大&#xff0c;所以现在已经有不少人开始使用它了。大屏幕上的教学效果很好&#xff0c;但如果只是为了用来看电视剧&#xff0c;那就没什么用了。如果你不想买一支价格昂贵的苹果电容笔&#xff0c;或只想用来做个学习笔记&#xff0c;这时&#xff0…

SpringBoot整合Nacos配置中心和注册中心

一、背景 公司项目中使用的Nacos作为服务的注册中心和配置中心&#xff0c;但是呢公司的这一套Nacos是经过封装了的&#xff0c;而且封装的不是很友好&#xff0c;想着自己搭建一套标注的Nacos配置中心和服务中心 二、Nacos配置中心和注册中心搭建 2.1 依赖引入 <!--注册…

端点中心配置

什么是桌面管理 桌面管理是管理组织内所有计算机系统的综合方法。尽管名称如此&#xff0c;桌面管理还包括监督组织内使用的笔记本电脑和其他计算设备。对于IT经理来说&#xff0c;使用户的计算机保持最新状态可能是一个挑战&#xff0c;特别是考虑到升级软件以防止安全漏洞的…

【Ubuntu18.04】Docker配置镜像源

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,YOLO领域博主爱笑的男孩。擅长深度学习,活动,YOLO,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个人简…

【二维矩阵如何存储在一维数组中(行优先和列优先)】

列优先和行优先的性能取决于具体的硬件架构和代码访问模式。在现代计算机中,内存访问的局部性(locality of reference)对性能至关重要。局部性分为两类:时间局部性(temporal locality)和空间局部性(spatial locality)。时间局部性表示最近访问过的数据项很可能在不久的…

加拿大留学思路自理

首先先看加拿大地图 留学加拿大的思路就应该是这样的&#xff1a; 1、清楚自己的需求 比如自己是移民向&#xff0c;所以首先就应该去加拿大官方网站Immigration and citizenship - Canada.ca 因为自己是理工科&#xff0c;之前在网络上看到别人总结的信息是说BC省理工类硕士…

【C++学习】类模板

类模板语法 #include<iostream> #include<string> using namespace std; //模板并不是万能的&#xff0c;有些特定数据类型&#xff0c;需要具体化方式做特殊实现 template<class NameType,class AgeType> class person { public:person(NameType name, Age…

k-means、决策树、svm算法总结

一、k-means算法 聚类算法&#xff1a; 一种典型的 无监督 学习算法&#xff0c;主要用于将相似的样本自动归到一个类别中。 在聚类算法中根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff0c;对于不同的相似度计算方法&#xff0c;会得到不同的聚类结果&…

【亲测有效】GnuTLS recv error (-110): The TLS connection was non-properly terminated.

【亲测有效】GnuTLS recv error [-110]: The TLS connection was non-properly terminated. 问题描述解决方法一&#xff1a;【取消代理】方法二【如果取消代理无用】方法三【这种方法对我有效】 问题描述 fatal: unable to access ‘https://github.com/openai/CLIP.git/’: …

JMeter压力测试案例(商品超卖并发问题)

什么要对接口压测呢? 压力测试可以用来验证软件系统的稳定性和可靠性&#xff0c;在压力下测试系统的性能和稳定性&#xff0c;发现并解决潜在的问题&#xff0c;确保系统在高负载情况下不会崩溃。压力测试可以用来评估软件系统的容量和性能&#xff0c;通过模拟高负载情况下…

2023年5月学习,6月考试DAMA-CDGA/CDGP数据治理认证

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中&#xff01; 考试开放地区&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证班进行中&#xff0c;快来报名加入学习吧&#xff01; DAMA认…

谈谈接口 0.0

目录 接口的概念 接口语法 接口的成员变量与方法 接口的使用 实现多个接口 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本上的USB口&#xff0c;电源插座等... 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘…

three.js 基础入门

总体思路&#xff1a; 1. 创建场景 2. 创建物体&#xff08;指定几何体、材质&#xff09; 3. 把物体加入场景 4. 创建相机&#xff08;指定机位及拍摄对象&#xff09; 5. 创建渲染器&#xff08;指定画布大小&#xff0c;渲染场景和相机&#xff09; // 1. 创建场景const s…