详解C语言如何计算结构体大小(结构体的内存对齐)

 更新时间:2023年07月21日 10:10:18   作者:可涵不会debug  
结构体的内存对齐是有关结构体内容的很重要一个知识点,主要考察方式是计算结构体的字节大小,所以本文就给大家详细介绍一下C语言如何计算结构体大小,文中的代码示例介绍的非常详细,需要的朋友可以参考下

引言:

当我们对计算结构体一无所知,我们不妨自己思索如何计算,是不是直接计算结构体成员变量占用内存的大小呢?

那我们先举个例子

struct s1
{
	int i;
	char a;
	char b;
};
struct s2
{
	char a;
	int i;
	char b;
};
int main()
{
	printf("%d\n", sizeof(struct s1));
	printf("%d\n", sizeof(struct s2));
	return 0;
}

观察发现结构体的大小计算跟我们想的很不一样。

不应该是两个char类型,一个int类型,2*1+4答案不应该是6吗?

上面两个结构体内容是一样的,只有顺序不一样,为何计算结果不一样呢?

我们就带着以上的疑问去探索!

一、计算偏移量

我们要研究明白结构体的成员列表在内存中到底是如何存储的,首先要知道结构体的各个成员变量在内存中相较于起始位置的偏移量。这时候要引用到offsetof,这个宏可以计算结构体成员相较于结构体起始位置的偏移量。

使用宏offsetof

 如何使用宏offsetof?

首先有头文件:#include<stddef.h>

参数是类型,和成员名,返回值就是结构体成员相较于结构体起始位置的偏移量。

我们先试着打印下s2各个成员关于结构体起始位置的偏移量。

发现结果是0、4、8,我们可以画一张内存图进行理解。

如图所示,根据offsetof我们可以得到这样的内存存储模式,但是这样一共也就9个字节,后面的3个字节从何而来?中间多出来的3个字节又从何而来?

我们继续探索。

结构体到底如何计算?

二、结构体的对齐规则

我们经过上面的分析,发现结构体成员不是按照顺序在内存中连续存放的,而是有一定的对齐规则,接下来我们就研究结构体的内存规则。

  • 结构体的第一个成员永远放在相较于结构体变量的起始未知的偏移量为0的位置
  • 从第二个成员开始,往后的每个成员都要对齐到某个对齐数的整数倍处。(对齐数:结构体成员自身大小和默认对齐数的较小值)VS上默认对齐数是8,gcc没有默认对齐数,对齐数就是变量本身的大小。
  • 结构体的总大小,必须是最大对齐数的整数倍,最大对齐数是:所有成员的对齐数中最大的值
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

三、总结计算方法

我们首先要知道结构体变量成员的自身字节大小,然后去寻找对齐数,对齐数的寻找方法就是将自身字节大小和默认对齐数比较,取较小值,这样先找到对齐数,然后根据自身的字节大小去填充,就完成了成员在内存中的存储,最后在所有的成员已经结束存储,再计算最大对齐数(所有成员的对齐数中最大值),这样就完成了计算!

我们既然已经知道规则和计算方法,就让我们小试牛刀一下~

四、练习

练习一:

struct s3
{
	double d;
	char c;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct s3));
	return 0;
}

上面图片的写法就是左边是本身成员变量的字节大小,右边是默认对齐数进行比较,最后再从对齐数中找出最大值,就是最大对齐数,所以最后0~15就是存储结构体的大小,也就是一共16个字节

 练习二:

struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S4));
	return 0;
}

上面是嵌套结构体场景,结构体S3本身大小是16,需要对齐到自身最大对齐数的位置,也就是8,然后double类型的对齐数是8,最后总字节大小也满足最大对齐数,所以一共32个字节。

五、为什么存在内存对齐?

1、平台原因

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

2、性能原因

数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说

结构体的内存对齐,就是让空间换时间。 

TIP:

我们在设计结构体时,可以人为的节省空间——让占用空间小的成员尽量集中在一起。

例如我们之前举的例子,尽管两个结构体存的成员变量一样,但是顺序不一样,结构体内存大小也是不同。

六、修改默认对齐数

对,你没有听错,默认对齐数是可以修改滴,当我们把默认对齐数修改为1时,结构体的成员变量就是连续存储的。代码如下,计算出来的大小就是4+1+8=13

#pragma pack(1)//修改默认对齐数为1
struct s
{
	int a;
	char b;
	double c;
};
#pragma pack()//修改默认对齐数为默认
int main()
{
	printf("%d\n", sizeof(struct s));
	return 0;
}

到此这篇关于详解C语言如何计算结构体大小(结构体的内存对齐)的文章就介绍到这了,更多相关C语言计算结构体大小内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++在vscode中的多文件编程问题解读

    C++在vscode中的多文件编程问题解读

    这篇文章主要介绍了C++在vscode中的多文件编程问题解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • C语言实现流星雨效果流程

    C语言实现流星雨效果流程

    C本篇文章带你用C语言去实现漫天流星雨的效果,代码写的很清晰,效果非常棒,另有视频详解整个过程,相信你一定能看懂,感兴趣的童鞋快来看看吧
    2021-11-11
  • C++ 结合 opencv读取图片与视频的方法

    C++ 结合 opencv读取图片与视频的方法

    这篇文章主要介绍了C++ 结合 opencv读取图片与视频,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • C/C++函数调用栈的实现方法

    C/C++函数调用栈的实现方法

    这篇文章主要介绍了C/C++函数调用栈的实现方法,可实现一个简单的脚本解释器,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-10-10
  • C语言中static与sizeof查缺补漏篇

    C语言中static与sizeof查缺补漏篇

    static在修饰变量的时候,如果是修饰全局变量,则跟全局变量功能一样;如果是修改局部变量,则每次调用的时候,保持着上一次的值;而sizeof是用来判断一个变量及数据类型所占字节数的,下面我们详细来看看
    2022-07-07
  • C++ Protobuf实现接口参数自动校验详解

    C++ Protobuf实现接口参数自动校验详解

    用C++做业务发开的同学是否还在不厌其烦的编写大量if-else模块来做接口参数校验呢?今天,我们就模拟Java里面通过注解实现参数校验的方式来针对C++ protobuf接口实现一个更加方便、快捷的参数校验自动工具,希望对大家有所帮助
    2023-04-04
  • c++中ref的作用示例解析

    c++中ref的作用示例解析

    这篇文章主要为大家介绍了c++中ref的作用示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • String底层函数的实现方式详解

    String底层函数的实现方式详解

    这篇文章主要介绍了String底层函数的实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • C++11中std::thread线程实现暂停(挂起)功能

    C++11中std::thread线程实现暂停(挂起)功能

    本文主要介绍了C++11中std::thread线程实现暂停(挂起)功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • C++实现读写ini配置文件的示例代码

    C++实现读写ini配置文件的示例代码

    配置文件的读取是每个程序必备的功能,配置文件的格式多种多样,例如:ini格式、json格式、xml格式等。其中属ini格式最为简单,且应用广泛。本文和大家分享了C++读写ini配置文件的方法,需要的可以参考一下
    2023-05-05

最新评论