Я запустил на своем компьютере следующую программу (64-разрядная версия Intel под управлением Linux).
#include <stdio.h>
void test(int argc, char **argv) {
printf("[test] Argc Pointer: %p\n", &argc);
printf("[test] Argv Pointer: %p\n", &argv);
}
int main(int argc, char **argv) {
printf("Argc Pointer: %p\n", &argc);
printf("Argv Pointer: %p\n", &argv);
printf("Size of &argc: %lu\n", sizeof (&argc));
printf("Size of &argv: %lu\n", sizeof (&argv));
test(argc, argv);
return 0;
}
Выход программы был
$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20
Размер указателя &argv
составляет 8 байт. Я ожидал, что адрес argc
будет, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8
но между ними есть 4-байтовые отступы. Почему это так?
Я предполагаю, что это может быть связано с выравниванием памяти, но я не уверен.
Я замечаю такое же поведение и с функциями, которые я вызываю.
c
memory-alignment
letmutx
источник
источник
main
.main
. В Cmain
может вызываться как обычная функция, поэтому она должна получать аргументы как обычная функция и должна подчиняться ABI.%zu
Ответы:
В вашей системе первые несколько целочисленных или указательных аргументов передаются в регистрах и не имеют адресов. Когда вы берете их адреса с помощью
&argc
или&argv
, компилятор должен сфабриковать адреса, записав содержимое регистра в ячейки стека и предоставив вам адреса этих расположений стека. При этом компилятор выбирает, в каком-то смысле, те места расположения стека, которые ему подходят.источник
С точки зрения языкового стандарта ответом является «без особой причины». C не указывает и не подразумевает какую-либо связь между адресами параметров функции. @EricPostpischil описывает, что, вероятно, происходит в вашей конкретной реализации, но эти детали будут другими для реализации, в которой все аргументы передаются в стек, и это не единственная альтернатива.
Более того, мне сложно найти способ, которым такая информация может быть полезна в программе. Например, даже если вы «знаете», что адрес
argv
составляет 12 байтов перед адресомargc
, все равно не существует определенного способа вычисления одного из этих указателей из другого.источник
uintptr_t
, и он, безусловно, не определяет отношения между адресами параметров или местом передачи аргументов.uintptr_t
может дать адрес в старших 24 битах и некоторые биты аутентификации в младших 8 битах. Затем добавление 4 просто облажает аутентификацию; это не обновляет…uintptr_t
вы получаете от преобразования не обязательно простой адрес.(void *)(uintptr_t)(void *)p
будет равна(void *)p
. И стоит отметить, что комитет прокомментировал практически эту проблему, заключив, что «реализации ... могут также рассматривать указатели, основанные на различном происхождении, как отличающиеся, даже если они поразрядно идентичны ».uintptr_t
преобразований адресов, а не разность указателей или «известное» расстояние в байтах. Конечно, это правда, но чем это полезно? Это остается верным , что «есть все еще не определен способ вычислить одну из этих указателей от другого» как ответ государств, но расчет не рассчитываетсяb
отa
а вычисляетb
от обоихa
иb
, посколькуb
должны быть использованы при вычитании для вычисления суммы добавить. Вычисление одного из другого не определено.