Pourquoi les adresses d'argc et d'argv sont-elles séparées de 12 octets?

40

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 &argvest de 8 octets. Je m'attendais à ce que l'adresse argcsoit, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8mais 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.

letmutx
la source
15
Pourquoi pas? Ils peuvent être séparés de 174 octets. La réponse dépendra de votre système d'exploitation et / ou d'une bibliothèque d'encapsuleur configurée pour main.
aschepler
2
@aschepler: Cela ne devrait dépendre d'aucun wrapper configuré pour main. En C, mainpeut ê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.
Eric Postpischil
@aschelper: je remarque également le même comportement pour les autres fonctions.
letmutx
4
C'est une «expérience de pensée» intéressante, mais vraiment, il n'y a rien qui devrait être plus qu'un «je me demande pourquoi». Ces adresses peuvent changer en fonction du système d'exploitation, du compilateur, de la version du compilateur, de l'architecture du processeur et ne doivent en aucun cas dépendre de la «vie réelle».
Neil
2
le résultat de sizeof doit être imprimé en utilisant%zu
phuclv

Réponses:

61

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 &argcou &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.

Eric Postpischil
la source
6
Notez que cela peut arriver même s'ils sont passés sur la pile ; le compilateur n'a aucune obligation d'utiliser l'emplacement de valeur entrante sur la pile comme stockage pour les objets locaux dans lesquels les valeurs entrent. Il peut être judicieux de le faire si la fonction finit par effectuer un appel de fin et a besoin des valeurs actuelles de ces objets pour produire les arguments sortants de l'appel de fin.
R .. GitHub STOP HELPING ICE
10

Pourquoi les adresses d'argc et d'argv sont-elles séparées de 12 octets?

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 argvest de 12 octets avant l'adresse de argc, il n'y a toujours aucun moyen défini de calculer l'un de ces pointeurs à partir de l'autre.

John Bollinger
la source
7
@ R..GitHubSTOPHELPINGICE: Le calcul de l'un à partir de l'autre est partiellement défini, pas bien défini. La norme C n'est pas stricte sur la façon dont la conversion uintptr_test 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.
Eric Postpischil
6
@ R..GitHubSTOPHELPINGICE: Le fait que vous puissiez aller-retour signifie que g (f (x)) = x, où x est un pointeur, f est convert-pointer-to-uintptr_t, et g est convert-uintptr_t-to -aiguille. Mathématiquement et logiquement, cela n'implique pas que g (f (x) +4) = x + 4. Par exemple, si f (x) était x² et g (y) était sqrt (y), alors g (f (x)) = x (pour x réel non négatif), mais g (f (x) +4) ≠ x + 4, en général. Dans le cas des pointeurs, la conversion en uintptr_tpeut 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…
Eric Postpischil
5
… Les bits d'adresse. Ou la conversion en uintptr_t peut donner une adresse de base dans les 16 bits hauts et un décalage dans les 16 bits bas, et l'ajout de 4 aux bits bas peut porter dans les bits hauts, mais la mise à l'échelle est incorrecte (car l'adresse représentée n'est pas base • 65536 + offset mais plutôt base • 64 + offset, comme c'était le cas dans certains systèmes). Tout simplement, le résultat uintptr_td'une conversion n'est pas nécessairement une simple adresse.
Eric Postpischil
4
@ R..GitHubSTOPHELPINGICE d'après ma lecture de la norme, il n'y a qu'une faible garantie qui (void *)(uintptr_t)(void *)psera 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 ".
Ryan Avella
5
@ R..GitHubSTOPHELPINGICE: Désolé, j'ai manqué que vous ajoutiez une valeur calculée comme étant la différence de deux uintptr_tconversions 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 à bpartir de amais calcule plutôt à bpartir des deux aet b, puisqu'il bdoit être utilisé dans la soustraction pour calculer le montant ajouter. Le calcul l'un de l'autre n'est pas défini.
Eric Postpischil