【C语言】结构体的大小是如何计算的?(结构体对齐)

简介: 【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;
}

运行结果为:

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

可以看到,这个结构体的大小是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;
}

运行结果为:

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

别急,我们再更改一下结构体内部的成员顺序:

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

再运行测试:

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

运行结果为:

为什么又是 8 而不是 12 了? 这两个结构体成员都是两个字符一个整形啊?

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

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


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

1.结构体成员的类型

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

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

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

C语言常见的数据类型及其所占空间

类型名 所占大小(单位:字节)
char 1
short 2

int

4
long 4/8(取决于系统)
float 4
double 8
long double 16

显示详细信息


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

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

画图理解一下:

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

#pragma pack(1)

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

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


结语

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

希望以上内容能对您有所帮助,如果文章中有纰漏的地方,欢迎各位大佬斧正,十分感谢!



相关文章
|
15天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
83 14
|
19天前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
80 10
|
20天前
|
存储 算法 安全
C 语言中的位运算:挖掘底层计算的高效力量
位运算是C语言中直接操作二进制位的一种技术,能高效处理底层数据,广泛应用于优化算法、硬件编程等领域,是掌握C语言高级特性的关键之一。
|
23天前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
29天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
101 13
|
23天前
|
存储 算法 C语言
C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项
本文深入探讨了C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项,并通过案例分析展示了实际应用,旨在帮助读者提高编程效率和代码质量。
68 4
|
29天前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
57 11
|
1月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
51 4
|
2月前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
72 10
|
2月前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。