- C编程教程
- C语言 - 首页
- C语言基础
- C语言 - 概述
- C语言 - 特性
- C语言 - 历史
- C语言 - 环境搭建
- C语言 - 程序结构
- C语言 - Hello World
- C语言 - 编译过程
- C语言 - 注释
- C语言 - 词法单元
- C语言 - 关键字
- C语言 - 标识符
- C语言 - 用户输入
- C语言 - 基本语法
- C语言 - 数据类型
- C语言 - 变量
- C语言 - 整数提升
- C语言 - 类型转换
- C语言 - 类型强制转换
- C语言 - 布尔值
- C语言中的常量和字面量
- C语言 - 常量
- C语言 - 字面量
- C语言 - 转义序列
- C语言 - 格式说明符
- C语言中的运算符
- C语言 - 运算符
- C语言 - 算术运算符
- C语言 - 关系运算符
- C语言 - 逻辑运算符
- C语言 - 位运算符
- C语言 - 赋值运算符
- C语言 - 一元运算符
- C语言 - 自增和自减运算符
- C语言 - 三元运算符
- C语言 - sizeof 运算符
- C语言 - 运算符优先级
- C语言 - 其他运算符
- C语言中的决策语句
- C语言 - 决策语句
- C语言 - if 语句
- C语言 - if...else 语句
- C语言 - 嵌套 if 语句
- C语言 - switch 语句
- C语言 - 嵌套 switch 语句
- C语言中的循环
- C语言 - 循环
- C语言 - while 循环
- C语言 - for 循环
- C语言 - do...while 循环
- C语言 - 嵌套循环
- C语言 - 无限循环
- C语言 - break 语句
- C语言 - continue 语句
- C语言 - goto 语句
- C语言中的函数
- C语言 - 函数
- C语言 - 主函数
- C语言 - 按值调用函数
- C语言 - 按引用调用函数
- C语言 - 嵌套函数
- C语言 - 可变参数函数
- C语言 - 用户自定义函数
- C语言 - 回调函数
- C语言 - 返回语句
- C语言 - 递归
- C语言中的作用域规则
- C语言 - 作用域规则
- C语言 - 静态变量
- C语言 - 全局变量
- C语言中的数组
- C语言 - 数组
- C语言 - 数组的特性
- C语言 - 多维数组
- C语言 - 向函数传递数组
- C语言 - 从函数返回数组
- C语言 - 变长数组
- C语言中的指针
- C语言 - 指针
- C语言 - 指针和数组
- C语言 - 指针的应用
- C语言 - 指针运算
- C语言 - 指针数组
- C语言 - 指向指针的指针
- C语言 - 向函数传递指针
- C语言 - 从函数返回指针
- C语言 - 函数指针
- C语言 - 指向数组的指针
- C语言 - 指向结构体的指针
- C语言 - 指针链
- C语言 - 指针与数组的区别
- C语言 - 字符指针和函数
- C语言 - 空指针
- C语言 - void 指针
- C语言 - 悬空指针
- C语言 - 解引用指针
- C语言 - 近指针、远指针和巨指针
- C语言 - 指针数组的初始化
- C语言 - 指针与多维数组的区别
- C语言中的字符串
- C语言 - 字符串
- C语言 - 字符串数组
- C语言 - 特殊字符
- C语言中的结构体和联合体
- C语言 - 结构体
- C语言 - 结构体和函数
- C语言 - 结构体数组
- C语言 - 自引用结构体
- C语言 - 查找表
- C语言 - 点(.)运算符
- C语言 - 枚举 (enum)
- C语言 - 结构体填充和打包
- C语言 - 嵌套结构体
- C语言 - 匿名结构体和联合体
- C语言 - 联合体
- C语言 - 位域
- C语言 - Typedef
- C语言中的文件处理
- C语言 - 输入 & 输出
- C语言 - 文件I/O (文件处理)
- C语言预处理器
- C语言 - 预处理器
- C语言 - 预处理指令
- C语言 - 预处理器运算符
- C语言 - 宏
- C语言 - 头文件
- C语言中的内存管理
- C语言 - 内存管理
- C语言 - 内存地址
- C语言 - 存储类
- 其他主题
- C语言 - 错误处理
- C语言 - 可变参数
- C语言 - 命令执行
- C语言 - 数学函数
- C语言 - static 关键字
- C语言 - 随机数生成
- C语言 - 命令行参数
- C语言编程资源
- C语言 - 问答
- C语言 - 快速指南
- C语言 - 速查表
- C语言 - 有用资源
- C语言 - 讨论
C语言中的结构体填充和打包
什么是C语言中的结构体填充?
C语言中的结构体填充是由CPU架构处理的过程。结构体填充在结构体中添加一定数量的空字节,以便数据成员在内存中自然对齐。对齐要求由处理器架构决定,而不是由语言本身决定。当然,对齐要求会根据某个CPU架构的数据总线大小或其他架构方面的考虑而改变。
通过示例理解结构体填充
让我们定义如下结构体类型:
struct struct1 { char x; int y; char z; };
示例1
让我们检查这种类型变量所需的字节大小:
#include <stdio.h> struct struct1{ char a; char b; int c; }; int main(){ printf("Size: %d", sizeof(struct struct1)); return 0; }
输出
运行此代码时,将产生以下输出:
Size: 8
结果与预期相反。
考虑到char类型需要1个字节,int类型需要4个字节,人们可能会认为输出应该是“1 + 1 + 4 = 6字节”。
但是,CPU架构需要更改此结构。考虑到我们使用的是具有32位处理器的CPU,它一次读取4个字节,这意味着一个字等于4个字节。
在一个CPU周期中,它访问char "a",然后是char "b"和int "c"的前两个字节。在第二个周期中,访问其他两个字节。
即使我们只想读取“c”,也需要两个CPU周期。为此,CPU在存储“c”值的字节之前添加两个空字节。这种机制称为填充。
现在解释了我们上面获得的结果,即结构体类型的尺寸为8字节。
示例2
让我们更改上述结构体成员的顺序,并设置“b”和“c”的类型。
#include <stdio.h> struct struct1{ char a; int b; char c; }; int main(){ printf("size: %d", sizeof(struct struct1)); return 0; }
输出
运行代码并检查其输出:
size: 12
在第一个字的4个字节中,第一个字节分配给char "a",后面跟着三个空字节。
构成下一个字的接下来的4个字节用于存储int "b"。随后,在接下来的4个字节组中,只有一个用于“c”。但是,结构体大小为12。
什么是C语言中的结构体打包?
另一方面,结构体打包是一种最小化填充影响的机制,从而尝试减少浪费的内存空间。我们可以使用某些pragma指令和属性来实现打包。
通过示例理解结构体打包
由CPU架构强制执行的填充是不可避免的,但是有一些方法可以最大限度地减少填充。可以使用以下方法:
- 使用 #pragma pack(1) 指令
- 使用 packed 属性
使用 #pragma pack(1) 指令
#pragma pack(1) 预处理器指令强制编译器忽略填充,并在内存分配过程中将结构体成员端对端对齐。
示例
让我们将此指令添加到前面使用的代码顶部,并查看结果:
#include <stdio.h> #pragma pack(1) struct struct1{ char a; int b; char c; }; int main(){ printf("size: %d", sizeof(struct struct1)); return 0; }
输出
运行代码并检查其输出:
size: 6
我们可以看到结构体填充已被避免,并减少了内存浪费。
使用 __attribute__((packed))
使用GCC,我们可以使用属性来指定结构体和联合体类型的各种特殊属性。这些属性是:aligned, deprecated, packed, transparent_union, unused 和 visibility。应用这些属性的语法是“__attribute__ ((...))”。
示例
在这里,我们将使用packed属性在我们的结构体类型定义中。
#include <stdio.h> struct __attribute__((packed)) struct1{ char a; int b; char c; }; int main(){ printf("size: %d", sizeof(struct struct1)); return 0; }
输出
运行代码并检查其输出:
size: 6
此方法也避免了填充的影响。