详解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语言五子棋小游戏实现代码

    C语言五子棋小游戏实现代码

    这篇文章主要为大家详细介绍了C语言五子棋小游戏实现代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 算法详解之分支限界法的具体实现

    算法详解之分支限界法的具体实现

    这篇文章主要介绍了算法详解之分支限界法的具体实现,需要的朋友可以参考下
    2014-02-02
  • C语言中无符号数和有符号数之间的运算

    C语言中无符号数和有符号数之间的运算

    C语言中有符号数和无符号数进行运算默认会将有符号数看成无符号数进行运算,其中算术运算默认返回无符号数,逻辑运算当然是返回0或1了。下面通过一个例子给大家分享C语言中无符号数和有符号数之间的运算,一起看看吧
    2017-09-09
  • C语言的动态内存管理你了解吗

    C语言的动态内存管理你了解吗

    这篇文章主要为大家详细介绍了C语言的动态内存管理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Qt 使用Poppler实现pdf阅读器的示例代码

    Qt 使用Poppler实现pdf阅读器的示例代码

    下面小编就为大家分享一篇Qt 使用Poppler实现pdf阅读器的示例代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-01-01
  • C++实现简单射击小游戏

    C++实现简单射击小游戏

    这篇文章主要为大家详细介绍了C++实现简单射击小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09
  • 彻底掌握C语言strcpy函数的用法

    彻底掌握C语言strcpy函数的用法

    C语言中的strcpy函数,是一种C语言的标准库函数,它用于对字符串进行复制。本章带你了解它的使用并模拟实现它
    2022-05-05
  • C++初始化列表学习

    C++初始化列表学习

    在C++中,struct和class的唯一区别是默认的克访问性不同,而这里我们不考虑访问性的问题,所以下面的代码都以struct来演示
    2013-09-09
  • C语言实现交换排序算法(冒泡,快速排序)的示例代码

    C语言实现交换排序算法(冒泡,快速排序)的示例代码

    这篇文章主要为大家详细介绍了如何利用C语言实现交换排序算法(冒泡排序、快速排序),文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-07-07
  • C语言数据类型和格式说明符基础教程示例

    C语言数据类型和格式说明符基础教程示例

    这篇文章主要为大家介绍了C语言数据类型和格式说明符基础教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12

最新评论