Структуры, определенные в /usr/include/linux/*
и /usr/include/asm-generic/*
, не кажутся упакованными, поэтому они зависят от используемого компилятора и семантики выравнивания указанного компилятора, верно?
В общем, это не так. Вот пример из GCC на 64-битной Ubuntu (/usr/include/x86_64-linux-gnu/asm/stat.h
):
struct stat {
__kernel_ulong_t st_dev;
__kernel_ulong_t st_ino;
__kernel_ulong_t st_nlink;
unsigned int st_mode;
unsigned int st_uid;
unsigned int st_gid;
unsigned int __pad0;
__kernel_ulong_t st_rdev;
__kernel_long_t st_size;
__kernel_long_t st_blksize;
__kernel_long_t st_blocks; /* Number 512-byte blocks allocated. */
__kernel_ulong_t st_atime;
__kernel_ulong_t st_atime_nsec;
__kernel_ulong_t st_mtime;
__kernel_ulong_t st_mtime_nsec;
__kernel_ulong_t st_ctime;
__kernel_ulong_t st_ctime_nsec;
__kernel_long_t __unused[3];
};
Смотрите __pad0
? int
обычно составляет 4 байта, но st_rdev
равен long
, что составляет 8 байтов, поэтому он должен быть выровнен по 8 байтов. Однако ему предшествует 3 дюйма = 12 байтов, поэтому добавляется 4 байта __pad0
.
По сути, реализация stdlib заботится о жестком кодировании своего ABI.
НО , что не верно для всех API. Вот struct flock
(с той же машины, /usr/include/asm-generic/fcntl.h
), используемый вызовом fcntl()
:
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_whence
и l_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);
}
Мы получаем:
$ 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 +++
Как видите, поля, следующие за l_whence
, действительно являются мусором.
Более того, C не имеет ABI , и поэтому эта хрупкая совместимость зависит от хорошей реализации игры. struct stat
выше предполагает, что компилятор не будет вставлять дополнительные случайные отступы.
ANSI C говорит:
Там также может быть безымянное заполнение в конце структуры или объединения,при необходимости, чтобы добиться правильного выравнивания, чтобы структура или объединение были частью массива.
Нет формулировки о том, как заполнение может быть вставлено в середину структурыпо причинам, отличным от выравнивания, однако есть также:
Поведение, определяемое реализацией
Каждая реализация должна документировать свое поведение в каждой из областей, перечисленных в этом разделе. Следующее определяется реализацией:
...
Заполнение и выравнивание элементов структур. Это не должно создавать проблем, если двоичные данные, записанные одной реализацией, не прочитаны другой.
На моей машине с Ubuntu и компилятор, и стандартная библиотека поступают из GCC, поэтому они бесперебойно взаимодействуют. Clang хочет расти, поэтому он совместим с GNU libc. Все просто играют хорошо, большую часть времени.