J'ai exécuté le programme suivant sur mon ordinateur (Intel 64 bits exécutant 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;
}
Le résultat du programme a été
$ 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
La taille du pointeur &argv
est de 8 octets. Je m'attendais à ce que l'adresse argc
soit, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8
mais il y a un remplissage de 4 octets entre eux. pourquoi est-ce le cas?
Je suppose que cela pourrait être dû à l'alignement de la mémoire, mais je ne suis pas sûr.
Je remarque également le même comportement avec les fonctions que j'appelle.
c
memory-alignment
letmutx
la source
la source
main
.main
. En C,main
peut être appelé comme une fonction régulière, il doit donc recevoir des arguments comme une fonction régulière et doit obéir à l'ABI.%zu
Réponses:
Sur votre système, les premiers arguments entiers ou pointeurs sont passés dans des registres et n'ont pas d'adresse. Lorsque vous prenez leurs adresses avec
&argc
ou&argv
, le compilateur doit fabriquer des adresses en écrivant le contenu du registre dans les emplacements de pile et en vous donnant les adresses de ces emplacements de pile. Ce faisant, le compilateur choisit, dans un sens, les emplacements de pile qui lui conviennent.la source
Du point de vue de la norme linguistique, la réponse est "pas de raison particulière". C ne spécifie ni n'implique aucune relation entre les adresses des paramètres de fonction. @EricPostpischil décrit ce qui se passe probablement dans votre implémentation particulière, mais ces détails seraient différents pour une implémentation dans laquelle tous les arguments sont passés sur la pile, et ce n'est pas la seule alternative.
De plus, j'ai du mal à trouver une manière dont ces informations pourraient être utiles dans un programme. Par exemple, même si vous "savez" que l'adresse de
argv
est de 12 octets avant l'adresse deargc
, il n'y a toujours aucun moyen défini de calculer l'un de ces pointeurs à partir de l'autre.la source
uintptr_t
est effectuée, et elle ne définit certainement pas les relations entre les adresses des paramètres ou l'endroit où les arguments sont passés.uintptr_t
peut donner une adresse dans les 24 bits hauts et quelques bits d'authentification dans les 8 bits bas. Ensuite, l'ajout de 4 ne fait que visser l'authentification; il ne se met pas à jour…uintptr_t
d'une conversion n'est pas nécessairement une simple adresse.(void *)(uintptr_t)(void *)p
sera comparable à(void *)p
. Et il vaut la peine de noter que le comité a commenté presque cette question exacte, concluant que "les implémentations ... peuvent également traiter les pointeurs basés sur des origines différentes comme distincts même s'ils sont identiques au niveau du bit ".uintptr_t
conversions d'adresses plutôt qu'une différence de pointeurs ou une distance "connue" en octets. Bien sûr, c'est vrai, mais en quoi est-ce utile? Il reste vrai qu '"il n'y a toujours pas de moyen défini de calculer l'un de ces pointeurs à partir de l'autre" comme l'indique la réponse, mais ce calcul ne calcule pas àb
partir dea
mais calcule plutôt àb
partir des deuxa
etb
, puisqu'ilb
doit être utilisé dans la soustraction pour calculer le montant ajouter. Le calcul l'un de l'autre n'est pas défini.