C Linux内核中结构填充/打包的语义是什么?
我对结构填充和打包的语义感兴趣,特别是与Linux内核返回的结构相关的语义 例如,如果编译一个程序+stdlib时不进行结构填充,而编译一个内核时不进行结构填充(不管怎样,IIRC是GCC的默认值),那么程序肯定无法运行,因为从内核返回的结构从它的角度看是垃圾 如果所讨论的编译器随着时间的推移更改了它的填充语义,那么肯定会出现同样的问题。C Linux内核中结构填充/打包的语义是什么?,c,linux-kernel,padding,memory-alignment,C,Linux Kernel,Padding,Memory Alignment,我对结构填充和打包的语义感兴趣,特别是与Linux内核返回的结构相关的语义 例如,如果编译一个程序+stdlib时不进行结构填充,而编译一个内核时不进行结构填充(不管怎样,IIRC是GCC的默认值),那么程序肯定无法运行,因为从内核返回的结构从它的角度看是垃圾 如果所讨论的编译器随着时间的推移更改了它的填充语义,那么肯定会出现同样的问题。/usr/include/linux/*和/usr/include/asm generic/*中定义的结构似乎没有打包,因此它们取决于所使用的编译器和所述编译器
/usr/include/linux/*
和/usr/include/asm generic/*
中定义的结构似乎没有打包,因此它们取决于所使用的编译器和所述编译器的对齐语义,对吗
但我可以在不同的计算机上使用不同的内存对齐要求和可能不同的填充语义,在我的现代计算机上运行多年前编译的二进制文件,它似乎工作得很好
它怎么看不到垃圾?这纯粹是运气吗?编译器作者(比如说TCC之类的)是否注意复制GCC的结构填充语义?在现实世界中如何处理这一潜在问题?
/usr/include/linux/*
中定义的结构,以及
/usr/include/asm generic/*
似乎未打包,因此
取决于所使用的编译器和所述的对齐语义
编译器,对吗
一般来说,这不是真的。下面是64位Ubuntu(/usr/include/x86_64-linux-gnu/asm/stat.h
)上的GCC示例:
请参见\uu pad0
int
通常是4个字节,但是st_rdev
是long
,它是8个字节,所以它必须是8字节对齐的。但是,它前面有3个int=12字节,因此添加了一个4字节的\uu pad0
基本上,stdlib的实现需要对其ABI进行硬编码
但并非所有API都是如此。以下是fcntl()
调用使用的struct flock
(来自同一台机器,/usr/include/asm generic/fcntl.h
):
struct flock {
short l_type;
short l_whence;
__kernel_off_t l_start;
__kernel_off_t l_len;
__kernel_pid_t l_pid;
__ARCH_FLOCK_PAD
};
正如您所看到的,在l\u where
和l\u start
之间没有填充。实际上,对于以下C程序,保存为abi.C
:
#include <fcntl.h>
#include <string.h>
int main(int argc, char **argv)
{
struct flock fl;
int fd;
fd = open("y", O_RDWR);
memset(&fl, 0xff, sizeof(fl));
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 200;
fl.l_len = 1;
fcntl(fd, F_SETLK, &fl);
}
如您所见,l\u之后的字段实际上是垃圾
此外,这种脆弱的兼容性依赖于实现的良好性<上面的code>struct stat
假设编译器不会插入额外的随机填充
ANSI C说:
如果结构或联合是数组的成员,则在结构或联合的末尾也可能有未命名的填充,以实现适当的对齐
<> P>>除了对齐以外的原因,在结构的中间没有插入填充词,但是也有:
实现定义的行为
每个实施应记录其在本节所列各方面的行为。定义了以下实现:
结构构件的填充和对齐。除非一个实现写入的二进制数据被另一个实现读取,否则这不会出现问题
在我的Ubuntu机器上,编译器和标准库都来自GCC,因此它们可以顺利地进行互操作。Clang希望增长,因此它与GNU libc兼容。大多数时候,每个人都玩得很好。啊,很有趣。我还没有读过
stat
定义。当然,由于struct stat
未被指定为\u属性(打包)
,那么编译器在填充元素之间添加额外填充的可能性很小?正确。C不保证“没有洞”,ANSI C字面上说“除非一个实现写入的二进制数据被另一个实现读取,否则这不会出现问题。”
#include <fcntl.h>
#include <string.h>
int main(int argc, char **argv)
{
struct flock fl;
int fd;
fd = open("y", O_RDWR);
memset(&fl, 0xff, sizeof(fl));
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 200;
fl.l_len = 1;
fcntl(fd, F_SETLK, &fl);
}
$ cc -g -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=200, l_len=1}) = 0
+++ exited with 0 +++
$ cc -g -fpack-struct -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=4294967296, l_len=-4294967296}) = 0
+++ exited with 0 +++