为什么默认情况下没有打包结构?

时间:2016-01-31 04:11:45

标签: c linux struct

在阅读CERT C编码标准时,我遇到了DCL39-C,它讨论了为什么Linux内核因信息泄漏而将解压缩的结构返回给用户空间通常是一个坏主意。

简而言之,结构通常不会默认打包,并且结构成员之间的填充字节通常包含未初始化的数据,因此信息泄漏。

为什么默认情况下不打包结构?我相信指南中提到它是特定架构的编译器的优化特性。为什么将结构对齐到某个字节大小更有效率,因为它浪费了内存空间?

另外,为什么C标准没有指定要求打包结构的标准化方法?我可以使用__attribute__((packed))向GCC询问,并且对于不同的编译器还有其他方法,但它似乎是一个很好的功能,作为标准的一部分。

3 个答案:

答案 0 :(得分:1)

数据通过并行线组(总线)通过电子电路传输。同样,电路本身倾向于并行排列。并联组件之间的物理距离为桥接它们的任何横向线增加了电阻和电容。因此,这种桥梁往往既昂贵又缓慢,计算机架构师在可能的情况下会避开它们。

未对齐的加载需要跨越通道移位字节。一些CPU(例如,面向效率的RISC)在物理上无法做到这一点,因为桥接组件不存在。有些人会检测到这种状况,并以一两个周期为代价介入一次换道。其他人可以在没有速度惩罚的情况下处理错位...假设分页内存不会增加另一个问题。

另一个完全不同的问题。存储器管理单元(MMU)位于CPU执行核心和存储器总线之间,将程序可见的逻辑地址转换为存储器芯片的物理地址。两个相邻的逻辑地址可能位于不同的芯片上。 MMU针对通常情况进行了优化,其中访问仅需要一次转换,但未对齐的访问可能需要两次。

跨越页面边界的错位访问可能会导致异常,这在内核中可能是致命的。由于页面相对较大,因此这种情况相对较少。它可能会逃避测试,而且可能是不确定的。

TL; DR:打包的结构不应该用于活动程序状态,尤其不能用于内核。它们可用于序列化和通信,但安全使用是另一个问题。

答案 1 :(得分:1)

离开结构"打开包装"允许编译器对齐所有成员,以便操作在这些成员上更有效(根据时钟时间,指令数量等)。类型的对齐要求取决于主机体系结构和(对于结构类型)所包含成员的对齐要求。

打包结构成员强制某些(如果不是全部)成员以对性能不利的方式对齐。在某些最坏的情况下 - 取决于主机架构 - 对未对齐变量(或未对齐的struct成员)的操作会触发处理器故障情况。例如,当加载或存储操作影响未对齐的地址时,RISC处理器体系结构会生成对齐错误。最近的x86架构上的一些SSE指令要求它们作用的数据在16字节边界上对齐。

在最好的情况下,由于将未对齐变量复制到对齐位置或寄存器,在那里进行操作并将其复制回来的开销,操作按预期行为,但效率较低。当涉及未对齐变量时,这些复制操作效率较低 - 毕竟,当变量对齐满足其设计要求时,处理器体系结构针对性能进行了优化。

如果您担心数据泄露出程序,只需使用memset()等函数在生命周期结束时覆盖结构内容(例如,在实例即将超出范围之前) ,或在使用free())释放动态分配的内存之前或之前。

或者使用操作系统(如OpenBSD),它会在将内存提供给进程或程序之前覆盖内存。请记住,这些功能往往会使操作系统和它所托管的程序运行效率降低。

C标准的最新版本(自2011年起)确实有一些工具来查询和控制变量的对齐(并影响结构成员的打包)。默认情况下,无论哪种对齐对主机体系结构最有效 - 对于结构类型通常意味着解压缩。

答案 2 :(得分:0)

在某些编译器(例如 Microchip XC8)上,所有结构确实总是被打包。

在某些平台上,编译器只会生成字节访问指令来访问压缩结构的成员,因为字节访问指令总是对齐的。如果所有结构都打包,则不使用 16 位、32 位和 64 位加载/存储指令。这显然是一种资源浪费。

C 标准没有指定封装结构的方式,可能是因为标准本身不知道 packing 的概念。由于结构的非位域成员的布局是实现定义的,因此超出了标准的范围。或者,该标准可能是为了支持总是在结构中添加填充的架构,因为这种架构在理论上确实是可行的。