Na swoim komputerze uruchomiłem następujący program (64-bitowy Intel z systemem 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;
}
Dane wyjściowe programu były
$ 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
Rozmiar wskaźnika &argv
wynosi 8 bajtów. Spodziewałem adres argc
, aby być address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8
, ale nie jest wyściółka 4 bajt pomiędzy nimi. Dlaczego tak jest?
Domyślam się, że może to wynikać z wyrównania pamięci, ale nie jestem pewien.
Zauważam to samo zachowanie w przypadku wywoływanych funkcji.
c
memory-alignment
letmutx
źródło
źródło
main
.main
. W Cmain
można wywoływać jako funkcję zwykłą, dlatego musi odbierać argumenty jak funkcja zwykła i musi być zgodny z ABI.%zu
Odpowiedzi:
W twoim systemie pierwsze argumenty liczb całkowitych lub wskaźników są przekazywane do rejestrów i nie mają adresów. Kiedy bierzesz ich adresy za pomocą
&argc
lub&argv
, kompilator musi sfabrykować adresy, pisząc zawartość rejestru do lokalizacji stosu i podając adresy tych lokalizacji stosu. W ten sposób kompilator wybiera w pewnym sensie dogodne dla niego lokalizacje stosu.źródło
Z punktu widzenia standardu językowego odpowiedź brzmi „bez konkretnego powodu”. C nie określa ani nie implikuje żadnego związku między adresami parametrów funkcji. @EricPostpischil opisuje to, co prawdopodobnie dzieje się w twojej konkretnej implementacji, ale te szczegóły byłyby inne dla implementacji, w której wszystkie argumenty są przekazywane na stos, i to nie jest jedyna alternatywa.
Co więcej, mam problem z wymyśleniem sposobu, w jaki takie informacje mogą być przydatne w programie. Na przykład, nawet jeśli „wiesz”, że adres
argv
wynosi 12 bajtów przed adresemargc
, nadal nie ma określonego sposobu obliczenia jednego z tych wskaźników z drugiego.źródło
uintptr_t
, a na pewno nie definiuje relacji między adresami parametrów lub w których przekazywane są argumenty.uintptr_t
może dać adres w wysokich 24 bitach i niektóre bity uwierzytelnienia w niskich 8 bitach. Następnie dodanie 4 po prostu psuje uwierzytelnianie; nie aktualizuje…uintptr_t
konwersja nie musi być prostym adresem.(void *)(uintptr_t)(void *)p
będzie porównywana równa(void *)p
. Warto zauważyć, że komitet skomentował prawie ten właśnie problem, stwierdzając, że „implementacje… mogą również traktować wskaźniki oparte na różnych źródłach jako odrębne, nawet jeśli są bitowo identyczne ”.uintptr_t
konwersji adresów zamiast różnych wskaźników lub „znanej” odległości w bajtach. Jasne, to prawda, ale jak to jest przydatne? Pozostaje prawdą, że „nadal nie ma określonego sposobu obliczenia jednego z tych wskaźników od drugiego”, jak stwierdza odpowiedź, ale obliczenia te nie obliczająb
z,a
lecz obliczająb
z obu,a
ib
ponieważb
należy je zastosować do odejmowania w celu obliczenia kwoty dodać. Obliczanie jednego od drugiego nie jest zdefiniowane.