Pourquoi un long int prend-il 12 octets sur certaines machines?

26

J'ai remarqué quelque chose d'étrange après avoir compilé ce code sur ma machine:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

Le résultat est le suivant. Notez qu'entre chaque adresse int, il y a une différence de 4 octets. Cependant, entre le dernier entier et le long entier, il y a une différence de 12 octets:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
la source
3
Mettez un autre intaprès hdans le code source. Le compilateur peut le mettre dans l'espace, avant h.
ctrl-alt-delor
32
N'utilisez pas la différence entre les adresses mémoire pour déterminer la taille. Il y a une sizeoffonction pour ça. printf("size: %d ", sizeof(long));
Chris Schneider
10
Vous n'imprimez que les 4 octets bas de vos adresses avec %x. Heureusement pour vous, il se trouve que cela fonctionne correctement sur votre plate-forme pour passer des arguments de pointeur avec une chaîne de format attendue unsigned int, mais les pointeurs et les entrées sont de tailles différentes dans de nombreux ABI. Permet %pd'imprimer des pointeurs en code portable. (Il est facile d'imaginer un système dans lequel votre code imprimerait les moitiés supérieure / inférieure des 4 premiers pointeurs, au lieu de la moitié inférieure des 8.)
Peter Cordes
5
@ChrisSchneider pour imprimer size_t use%zu . @yoyo_fun pour imprimer les adresses utilisées%p . Utiliser le mauvais spécificateur de format appelle un comportement indéfini
phuclv
2
@luu ne répand pas la désinformation. Aucun compilateur décent ne se soucie de l'ordre dans lequel les variables sont déclarées en C. S'il s'en soucie, il n'y a aucune raison pour qu'il le fasse comme vous le décrivez.
gnasher729

Réponses:

81

Cela n'a pas pris 12 octets, il n'en a fallu que 8. Cependant, l' alignement par défaut pour un entier de 8 octets sur cette plate-forme est de 8 octets. En tant que tel, le compilateur devait déplacer le long int vers une adresse qui est divisible par 8. L'adresse "évidente", da54dc8c, n'est pas divisible par 8 d'où l'écart de 12 octets.

Vous devriez pouvoir tester cela. Si vous ajoutez un autre int avant le long, donc il y en a 8, vous devriez trouver que le long int sera bien aligné sans bouger. Ce ne sera plus que 8 octets à partir de l'adresse précédente.

Il vaut probablement la peine de souligner que, bien que ce test devrait fonctionner, vous ne devez pas vous fier aux variables organisées de cette façon. Le compilateur AC est autorisé à faire toutes sortes de choses géniales pour essayer de faire fonctionner votre programme rapidement, y compris en réorganisant les variables (avec quelques mises en garde).

Alex
la source
3
différence, pas écart.
Déduplicateur
10
msgstr "y compris les variables de réorganisation". Si le compilateur décide que vous n'utilisez pas deux variables en même temps, il est également possible de les superposer partiellement ou de les superposer complètement ...
Roger Lipscombe
8
Ou bien, conservez-les dans des registres plutôt que sur la pile.
Arrêtez de nuire à Monica du
11
@OrangeDog Je ne pense pas que cela se produirait si l'adresse est prise comme dans ce cas mais, en général, vous avez bien sûr raison.
Alex
5
@Alex: Vous pouvez obtenir des choses amusantes avec la mémoire et les registres lorsque vous prenez l'adresse. Prendre l'adresse signifie qu'il doit lui donner un emplacement mémoire, mais ne signifie pas qu'il doit réellement l'utiliser. Si vous prenez l'adresse, attribuez-lui 3 et passez-la à une autre fonction, elle peut simplement écrire 3 dans RDI et appeler, sans jamais l'écrire en mémoire. Surprenant dans un débogueur parfois.
Zan Lynx
9

En effet, votre compilateur génère un remplissage supplémentaire entre les variables pour garantir qu'elles sont correctement alignées en mémoire.

Sur la plupart des processeurs modernes, si une valeur a une adresse qui est un multiple de sa taille, il est plus efficace d'y accéder. S'il avait été placé hau premier emplacement disponible, son adresse aurait été 0xda54dc8c, qui n'est pas un multiple de 8, donc aurait été moins efficace à utiliser. Le compilateur le sait et ajoute un peu d'espace inutilisé entre vos deux dernières variables afin de s'assurer que cela se produit.

Jules
la source
Merci pour l'explication. Pouvez-vous m'indiquer certains documents concernant les raisons pour lesquelles l'accès à des variables qui sont multiples de leur taille est plus efficace? je voudrais savoir pourquoi cela se produit?
yoyo_fun
4
@yoyo_fun et si vous voulez vraiment comprendre la mémoire, alors il y a un article célèbre sur le sujet futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
1
@yoyo_fun C'est assez simple. Certains contrôleurs de mémoire ne peuvent accéder qu'à des multiples de la largeur de bits du processeur (par exemple, un processeur 32 bits peut uniquement demander directement les adresses 0-3, 4-7, 8-11, etc.). Si vous demandez une adresse non alignée, le processeur doit faire deux demandes de mémoire puis entrer les données dans le registre. Donc, revenons à 32 bits, si vous vouliez une valeur stockée à l'adresse 1, le processeur doit demander les adresses 0-3, 4-7, puis obtenir les octets de 1, 2, 3 et 4. Quatre octets de mémoire perdue.
phyrfox
2
Point mineur, mais l'accès à la mémoire mal aligné peut être une faute irrécupérable au lieu d'un impact sur les performances. Dépend de l'architecture.
Jon Chesterfield
1
@JonChesterfield - Oui. Voilà pourquoi je commentais que la description que j'ai donné applique à la plupart des architectures modernes (je voulais dire la plupart du temps x86 et ARM). Il y en a d'autres qui se comportent de différentes manières, mais ils sont beaucoup moins courants. ( Il est intéressant: ARM utilisé comme l' une des architectures qui exigeaient l' accès alignés, mais ils ont ajouté la gestion automatique des accès non alignés dans les versions ultérieures)
Jules
2

Votre test ne teste pas nécessairement ce que vous pensez qu'il est, car il n'y a aucune exigence de la langue pour relier l'adresse de l'une de ces variables locales les unes aux autres.

Vous devez les placer sous forme de champs dans une structure afin de pouvoir déduire quelque chose sur l'allocation de stockage.

Les variables locales ne sont pas tenues de partager le stockage côte à côte d'une manière particulière. Le compilateur peut insérer une variable temporaire n'importe où dans la pile, par exemple, qui pourrait se trouver entre deux de ces variables locales.

En revanche, il ne serait pas autorisé d'insérer une variable temporaire dans une structure, donc si vous imprimiez les adresses des champs de structure à la place, vous compareriez les éléments prévus alloués à partir du même mandrin logique de mémoire (la structure).

Erik Eidt
la source