C语言中计算结构体的大小

一. 使用sizeof 计算结构体的大小

通常情况下,我们习惯于使用 sizeof 运算符来计算结构体的大小

例如,下面是一个结构体的定义:

struct Student {
    int id;
    char name[20];
    int age;
    float score;
};

其中,Student是该结构体的类型名,而id,name,age,score则是该结构体的成员

接着我们在主函数内部创建一个结构体变量s。这时我们就可以使用sizeof运算符来计算这个结构体的大小了。如,直接使用sizeof操作符计算变量s的大小:

#include <stdio.h>
 
struct Student {
    int id;
    char name[20];
    int age;
    float score;
};
 
int main()
{
    struct Student s;
    printf("Size of struct Student is %d bytes\n", sizeof(s));
    return 0;
}

运行结果为:

./test
Size of struct Student is 32 bytes

当然我们也可以不创建变量,直接将结构体类型放入sizeof中来计算该结构体类型的大小

int main()
{
    struct Student s;
    printf("Size of struct Student is %d bytes\n", sizeof(struct Student));
    return 0;
}

运行结果仍然是:

./test            
Size of struct Student is 32 bytes

可以看到,这个结构体的大小是32个字节

这是由于int类型占用4个字节,char类型占用1个字节,float类型占用4个字节,而且结构体中的成员顺序是按照定义的顺序来排列的。

因此我们似乎很容易就能计算出这个结果:4+20+4+4=32字节

但事实上 结构体的大小并不是通过这样简单累加计算的,如创建如下结构体:

struct stu
{
	char ch1;
	int i;
	char ch2;
};

然后使用sizeof计算该结构体的大小

#include<stdio.h>
struct stu
{
	char ch1;
	int i;
	char ch2;
};
 
int main()
{
	printf("Size of struct stu is %d bytes\n", sizeof(struct stu));
	return 0;
}

运行结果为:

./test123            
Size of struct stu is 12 bytes

为什么是12,而不是 1+4+1=6?

别急,我们把结构体内部成员的顺序再调整一下

struct stu
{
	char ch1;
	char ch2;
	int i;
};

运行结果为:

 ./test123            
Size of struct stu is 8 bytes

为什么是8 而不是12了呢?这两个结构体内部的成员没有改变,只是改变了位置,结果却不同。

通过以上测试,我们很容易发现,首先结构体的大小不是简单的每个成员大小逐个累加。其次,结构体的大小似乎和结构体成员的顺序也有关系

那么结构体的大小到底是如何计算的呢?下面我们一起探究一下。

二. 影响结构体大小的因素

1. 结构体成员的类型

首先的影响因素就是结构体成员的类型,不同的结构体成员占用的内存大小不同。

如,一个int类型的成员占用4个字节,一个char类型的成员占用1个字节。

而C语言中常见的变量类型及其所占空间字节数如下表:

C语言常见的数据类型及其所占空间
类型名所占大小(单位:字节)
char1
short2

int

4
long4/8(取决于系统)
float4
double8
long double16

 2. 结构体成员的对齐方式

为了提高内存访问的效率,编译器会对结构体进行对齐。对齐的方式是按照成员的类型和顺序来进行的。

对齐的目的是为了让结构体成员的地址能够被整除,从而提高内存访问的速度。

3.结构体成员的顺序

结构体成员的顺序也会影响结构体的大小。

如果结构体成员的顺序不合理,可能会导致结构体的大小变得更大。

就像上面我们举的那个例子一样,结构体内部都是两个字符型数据和一个整形数据,但因为顺序不同,结构体的大小可能就完全不一样了。

三. 利用结构体对齐规律计算结构体大小

1. 结构体的对齐规则:

要知道结构体大小是如何计算的,首先需要了解结构体的对齐规则:

  1. 第一个成员在于结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数(VS 中默认的为8) 与该成员大小的较小值。
  3. 结构体总大小为最大对齐数(每个成员变量都有自己的对齐数)的整数倍。
  4. 针对嵌套结构体,嵌套的结构体要对齐到自己最大对齐数的整数倍处,结构体总大小是所有对齐数的最大值(包含嵌套结构体的对齐数)的整数倍

只看定义理解可能有些抽象,我们接下来画图举例说明一下这些对齐规则,如下面这个结构体:

struct stu
{
	char ch1;
	int i;
    char ch2;
};

我们在前面运行过它的大小为12,而它的计算过程如下:

 理解了这个结构体的大小是如何计算的,我们再来看看调整顺序后它为何又变成8了:

struct stu
{
	char ch1;
	char ch2;
	int i;
};

 理解了这两个结构体的内存大小是如何计算得出的,还有一种情况是当结构体中有成员是数组类型时,我们并不能将整个数组视为一整个成员,而是需要将数组中的元素拆开来继续一个一个对齐,直到排完最后一个数组元素为止

如结构体中包含字符数组ch:

char ch[5];

在排列时就应该将该数组视为:

char ch1;
char ch2;
char ch3;
char ch4;
char ch5;

然后再将这些元素一一对齐在结构体即可。

2.结构体对齐的原因:

结构体对齐大致可以分为两个原因:

1> 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2> 性能原因:

内存对齐是指将变量存储在内存中时,按照一定的规则将变量的地址调整为某个特定值的过程。这个特定值通常是变量所占用的空间大小的整数倍。

结构体中的成员变量有可能会存在空洞,即某些成员变量之间的字节没有被使用。

这是因为编译器为了保证结构体成员变量的地址是按照一定规则对齐的,会在成员变量之间插入一些空字节。

这样做的好处是,可以提高程序的运行效率,因为当变量的地址按照一定规则对齐时,CPU可以更快地读取变量的值。

例如,一个结构体中包含一个char类型和一个int类型的成员变量,char 类型占用1个字节,int类型占用4个字节。如果不进行内存对齐,那么这个结构体的大小应该是5个字节,但是由于int类型的地址必须是4的倍数,因此编译器会在char 类型后面插入3个空字节,使得int类型的地址是4的倍数。这样,结构体的大小就变成了8个字节,其中3个字节是空洞。

图解如下:

3. 如何修改默认对齐数:

而有时我们会碰到结构体对齐方式不合适的时候,这时我们是可以自己修改系统默认对齐数的,如:

#include<stdio.h>
 
#pragma pack(2)
 
struct stu
{
	char ch1;
    int i;
	char ch2;
};
 
int main()
{
	printf("Size of struct stu is %d bytes\n", sizeof(struct stu));
	return 0;
}

 我们在一开始使用#pragma pack(2) 语句将系统默认对齐数改为了2.

修改后的运行结果则变成了:

./test123            
Size of struct stu is 8 bytes

画图理解一下:

 注意,当我们将默认对齐数改为1时,即:

#pragma pack(1)

则相当于没有对齐数,结构体的大小就是按顺序累加了,如:

./test123            
Size of struct stu is 6 bytes

将默认对齐数改为1后,如上结构体的大小就变成1+4+1=6了。

在了解了结构体的对齐方式后,我们不仅可以准确的计算出结构体的大小,还可以依据对齐规则合理调整成员顺序,以减少结构体的内存浪费。同时可以通过修改对齐数来自由选择用空间换时间,还是用时间换空间。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IP被封怎么办?如何绕过IP禁令?

相信很多人遇到过IP禁令&#xff1a;比如你在访问社交媒体、搜索引擎或电子商务网站时会被限制访问&#xff0c;又或者你的的账号莫名被封&#xff0c;这些由于网络上的种种限制我们经常会遭遇IP被封的情况&#xff0c;导致无法使用继续进行网络行动。在本文中&#xff0c;我们…

Django笔记(七):JWT认证

首 前后端分离的项目更多使用JWT认证——Json Web Token。本文记录djangorestframework-simplejwt的使用方式。文档 安装 pip install djangorestframework-simplejwt 配置settings.py: INSTALLED_APPS [rest_framework_simplejwt, ]REST_FRAMEWORK {DEFAULT_AUTHENTICA…

ARM 400系列控制器IP简介

1. GIC-400 GIC-400是一个高性能、区域优化的中断控制器&#xff0c;具有高级微控制器总线架构&#xff08;AMBA&#xff09;高级可扩展接口&#xff08;AXI&#xff09;接口。它在片上系统&#xff08;SoC&#xff09;配置中检测、管理和分配中断。你可以对GIC-400进行配置&am…

shell脚本基础之循环语句

目录 一、循环语句的概念 二、for循环语句 1、列表循环 2、列表for循环案例大全 案例一 案例二 案例三 案例四 案例五 案例六 案例七 案例八 3、不带列表循环 4、类似C语言风格的for循环 5、for循环总结 三、while循环语句 1、while循环语句格式 2、while死循…

概念性——数据库简介

前些天发现了一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站。 概念性——数据库简介 介绍 数据对于当今许多应用程序和网站的运行至关重要。对热门视频的评论、多人游戏中分…

精酿啤酒的原料供应链:质量控制的重要性

对于啤酒的品质和口感&#xff0c;原料供应链的质量控制是重要的。特别是对于Fendi Club这样品质的啤酒&#xff0c;其原料供应链的管理更是重中之重。下面&#xff0c;我们将深入探讨Fendi Club啤酒如何对其原料供应链进行质量控制&#xff0c;以确保啤酒的品质和口感。 首先&…

专业144总分410+华南理工大学811信号与系统考研经验华工电子信息与通信

今年专业811信号与系统144&#xff08;二战&#xff0c;感谢信息通信Jenny老师专业课对我的巨大提高&#xff0c;第一年自己复习只考了90&#xff0c;主要栽专业课和数学&#xff09;总分410含泪&#xff08;二战的同学都知道苦&#xff0c;成功来之不易&#xff09;考上华南理…

【IC设计】Vivado单口RAM的使用和时序分析

文章目录 创建单口RAM IPIP Catalog中选择单口RAM IPBasicPort A OptionsOther Options 仿真找到IP例化原语编写Testbench 波形分析 创建单口RAM IP IP Catalog中选择单口RAM IP Basic Port A Options Other Options 仿真 找到IP例化原语 IP Sources-Instantiation Template…

深度了解TCP/IP模型

网络通信是现代社会不可或缺的一部分&#xff0c;而TCP/IP模型作为网络通信的基石&#xff0c;扮演着至关重要的角色。本文将深入探讨TCP/IP模型的概念、结构及其在网络通信中的作用&#xff0c;为读者提供全面的了解。 一.TCP/IP模型简介 TCP/IP模型是一个网络通信协议体系&a…

Android Settings 按住电源按钮

如题&#xff0c;Android 原生 Settings 里有个 按住电源按钮 的选项&#xff0c;可以设置按住电源按钮的操作。 按住电源按钮 两个选项的 UI 是分离的&#xff0c; 电源菜单 代码在 packages/apps/Settings/src/com/android/settings/gestures/LongPressPowerForPowerMen…

使用WebDriver采样器将JMeter与Selenium集成

第一步&#xff1a; 在JMeter中添加Selenium / WebDriver插件 第二步&#xff1a; 创建一条测试计划–添加线程组 添加配置元素 - jpgc - WebDriver Sampler 添加配置元素 - jpgc - Chrome Driver Config 并且添加监听器查看结果树 第三步&#xff1a; 下载 chromedriver…

威士忌的类型:从单一麦芽到混合威士忌

威士忌&#xff0c;这种源自苏格兰的特别蒸馏酒&#xff0c;如今已在全球范围内赢得了无数赞誉。其类型多样&#xff0c;从单一麦芽到混合威士忌&#xff0c;每一种都有其特别的特点和风味。本文将深入探讨不同类型的威士忌&#xff0c;以雷盛537威士忌的实例&#xff0c;分析其…

AWS 专题学习 P9 (API Gateway、Cognito、Serverless SA)

文章目录 专题总览API GatewayAPI Gateway – 高级集成API Gateway – AWS 服务集成API Gateway - Endpoint TypesAPI Gateway – 安全 AWS Step Functions&#xff08;AWS 步骤函数&#xff09;Amazon Cognito&#xff08;认证服务&#xff09;Cognito User Pools (CUP) – 用…

【云原生】Docker网络模式和Cgroup资源限制

目录 一、Docker 网络实现原理 二、Docker 的网络模式 #网络模式详解&#xff1a; 第一种&#xff1a;host模式 第二种&#xff1a;bridge模式 第三种&#xff1a;container模式 第四种&#xff1a;none模式 第五种&#xff1a;自定义网络 三、Cgroup资源控制 第一种&a…

中断与事件区别

自记&#xff1a; 以外部中断举例&#xff1a; 这张图是一条外部中断线或外部事件线的示意图, 图中信号线上划有一条斜线,旁边标志19字样的注释,表示这样的线路共有19套. 图中的蓝色虚线箭头,标出了外部中断信号的传输路径,首先外部信号从编号1的芯片管脚进入,经过编号2的边…

Java实现校园疫情防控管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、系统展示四、核心代码4.1 新增健康情况上报4.2 查询健康咨询4.3 新增离返校申请4.4 查询防疫物资4.5 查询防控宣传数据 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBoot…

php实现多进程的几种方式

目录 一&#xff1a;使用pcntl扩展库 二&#xff1a;使用Swoole扩展 三&#xff1a;使用多进程模式PHP-FPM 在PHP中实现多进程主要有以下几种方式&#xff1a; 一&#xff1a;使用pcntl扩展库 pcntl扩展库提供了多线程相关的函数&#xff0c;如pcntl_fork()用于创建子进程…

vue常用指令(v-show)

一、v-show 指令 作用: 根据真假值,切换元素的显示状态 二、代码演示 1、v-show 绑定判断条件后&#xff0c;根据布尔值决定是否显示图片 不显示图片 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…

pve8.1 安装、创建centos7虚拟机及配置

之前创建虚拟机centos7时&#xff0c;硬盘分配太大了&#xff0c;做成模板后无法进行修改了&#xff0c;安装完pve8.1后&#xff0c;强迫症犯了重新创建一下顺便记录一下配置过程。由于目前centos7还是生产用的比较多的版本所以本次还是安装centos7.9版本。 一、下载镜像 下载…

mysql 多版本并发控制mvcc

行级锁的一个变种避免了加锁&#xff0c;开销低非阻塞读操作&#xff0c;写操作是行级原理&#xff1a;使用数据在某个时间点的快照实现&#xff0c;不同事务在同一个时间可以看到同一个表中的不同数据。分类&#xff1a;乐观&#xff0c;悲观在一行中存储更多数据&#xff0c;…