Если бы вы скомпилировали это с помощью компилятора C и посмотрели на вывод asm, вы бы увидели, что он просто передает указатель в структуру.
C создает анонимный массив из struct timespec[]
, который является lvalue, и, таким образом, он может "затухать" до указателя при передаче в
int nanosleep(const struct timespec *req, struct timespec *rem);
Если вы посмотрите справочную страницу системного вызова, вы увидите, что оба аргумента принимают за указатели.
На самом деле нет системных вызовов POSIX, которые принимают struct args по значению . Этот выбор дизайна имеет смысл, поскольку не все соглашения о вызовах во всех архитектурах обрабатывают структуры передачи одинаково. Соглашения о вызовах системных вызовов часто не совпадают точно с соглашениями о вызовах функций, и, как правило, не имеют правил ни для чего, кроме целочисленных типов / указателей.
Системные вызовы обычно ограничены максимум 6 аргументами, без возврата к памяти стека для больших аргументов. Ядру необходим общий механизм для сбора аргументов из пространства пользователя и отправки их в функцию ядра из таблицы указателей функций, поэтому все системные вызовы должны иметь сигнатуры, совместимые с syscall(uintptr_t a, uintptr_t b, ... uintptr_t f)
на уровне asm.
Если операционная система вводит системный вызов, который принимает структуру по значению, она должна будет определить детали ABI для передачи его в каждой поддерживаемой архитектуре. Это может быть сложно, например 16-байтовая структура, такая как struct timespec
в 32-битной архитектуре, заняла бы 4 слота передачи аргументов ширины регистра. (Предположим, что время еще 64-битное, в противном случае у вас проблема с опрокидыванием year-2038 .)
Как говорит Маттео, x86-64 System V упаковывает до 16 байтов в до 2 регистров для вызова функций . Правила хорошо документированы в ABI , но обычно проще всего написать простую тестовую функцию, которая сохраняет свои аргументы в volatile long x
или возвращает одно из них, и скомпилировать ее с включенной оптимизацией.
например. на Годболте
#include <stdint.h>
struct padded {
int16_t a;
int64_t b;
};
int64_t ret_a(int dummy, padded s) { return s.a; }
int64_t ret_b(int dummy, padded s) { return s.b; }
Компилирует для x86-64 System V в этот ассемблер, поэтому мы можем видеть, что структура передается в RDX: RSI с неиспользуемыми старшими 6 байтами RSI (потенциально удерживающими мусор), так же, как представление объекта в памяти с 6 байты заполнения, поэтому элемент int64_t
имеет выравнивание alignof(int64_t) = 8
.
ret_a(int, padded):
movsx rax, si
ret
ret_b(int, padded):
mov rax, rdx
ret
Запись вызывающего, который помещает аргументы в правильные регистры, должна быть очевидной.