82 votes

Conflit entre un tutoriel de Stanford et GCC

Selon ce film (autour de la minute 38), si j'ai deux fonctions avec les mêmes vars locaux, elles utiliseront le même espace. Le programme suivant devrait donc imprimer 5 . En le compilant avec gcc résultats -1218960859 . Pourquoi?

Le programme:

 #include <stdio.h>

void A()
{
    int a;
    printf("%i",a);
}

void B()
{
    int a;
    a = 5;
}

int main()
{
    B();
    A();
    return 0;
}
 

comme demandé, voici la sortie du désassembleur:

 0804840c <A>:
 804840c:   55                      push   ebp
 804840d:   89 e5                   mov    ebp,esp
 804840f:   83 ec 28                sub    esp,0x28
 8048412:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048415:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048419:   c7 04 24 e8 84 04 08    mov    DWORD PTR [esp],0x80484e8
 8048420:   e8 cb fe ff ff          call   80482f0 <printf@plt>
 8048425:   c9                      leave  
 8048426:   c3                      ret    

08048427 <B>:
 8048427:   55                      push   ebp
 8048428:   89 e5                   mov    ebp,esp
 804842a:   83 ec 10                sub    esp,0x10
 804842d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048434:   c9                      leave  
 8048435:   c3                      ret    

08048436 <main>:
 8048436:   55                      push   ebp
 8048437:   89 e5                   mov    ebp,esp
 8048439:   83 e4 f0                and    esp,0xfffffff0
 804843c:   e8 e6 ff ff ff          call   8048427 <B>
 8048441:   e8 c6 ff ff ff          call   804840c <A>
 8048446:   b8 00 00 00 00          mov    eax,0x0
 804844b:   c9                      leave  
 804844c:   c3                      ret    
 804844d:   66 90                   xchg   ax,ax
 804844f:   90                      nop
 

130voto

Jonathon Reinhart Points 40535

Oui, oui, c'est un comportement indéfini, parce que vous êtes à l'aide de la variable non initialiséeà 1.

Cependant, sur l'architecture x862, cette expérience devrait fonctionner. La valeur n'est pas "effacé" de la pile, et puisqu'il n'est pas initialisé en B(), la même valeur doit toujours être là, à condition que la pile d'images sont identiques.

Je me risquerais à deviner que, depuis int a n'est pas utilisé à l'intérieur de l' void B(), le compilateur optimisé que le code, et à 5 n'a jamais été écrite à cet endroit sur la pile. Essayez d'ajouter un printf en B() - il juste de travail.

Aussi, les drapeaux de compilation - à savoir le niveau d'optimisation - est susceptible d'affecter cette expérience. Essayez de désactiver les optimisations en passant -O0 de gcc.

Edit: je viens de compiler votre code avec gcc -O0 (64-bit), et en effet, le programme imprime 5, comme un familier avec la pile d'appel s'attendre. En fait, cela a fonctionné, même sans -O0. Un 32-bits peuvent se comporter différemment.

Avertissement: Ne jamais, jamais utiliser quelque chose comme cela dans la "vraie" code!

1 - Il y a un débat en cours sur ci-dessous quant à savoir si ou non ce qui est officiellement "UB", ou tout simplement imprévisible.

2 - Aussi x64, et probablement tous les autres de l'architecture qui utilise une pile d'appel (au moins avec une MMU)


Jetons un coup d'oeil à une raison pour laquelle il n'a pas de travail. C'est mieux vu en 32 bits, alors je vais compiler avec -m32.

$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)

J'ai compilé avec $ gcc -m32 -O0 test.c (Optimisations désactivé). Lorsque je l'exécute, il imprime des ordures.

En regardant $ objdump -Mintel -d ./a.out:

080483ec <A>:
 80483ec:   55                      push   ebp
 80483ed:   89 e5                   mov    ebp,esp
 80483ef:   83 ec 28                sub    esp,0x28
 80483f2:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80483f5:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80483f9:   c7 04 24 c4 84 04 08    mov    DWORD PTR [esp],0x80484c4
 8048400:   e8 cb fe ff ff          call   80482d0 <printf@plt>
 8048405:   c9                      leave  
 8048406:   c3                      ret    

08048407 <B>:
 8048407:   55                      push   ebp
 8048408:   89 e5                   mov    ebp,esp
 804840a:   83 ec 10                sub    esp,0x10
 804840d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048414:   c9                      leave  
 8048415:   c3                      ret    

Nous voyons que dans l' B, le compilateur réservés 0x10 octets d'espace de pile, et initialisé notre int a variable [ebp-0x4] - 5.

En A cependant, le compilateur placé int a à [ebp-0xc]. Dans ce cas, les variables locales n'ont pas les retrouver à la même place! Par l'ajout d'un printf() appel en A ainsi sera la cause de la pile d'images pour A et B à l'identique, et de les imprimer 55.

36voto

Joachim Pileborg Points 121221

C'est un comportement indéfini . Une variable locale non initialisée a une valeur indéterminée et son utilisation entraînera un comportement indéfini.

12voto

cyriel Points 1320

Une chose importante à se rappeler - ne jamais compter sur quelque chose comme ça et de ne jamais l'utiliser dans le code réel! C'est juste une chose intéressante(qui même n'est pas toujours vrai), et non une fonction ou quelque chose comme ça. Imaginez-vous en essayant de trouver un bug produite par ce genre de "fonctionnalité" - cauchemar.

Btw. - Le C et le C++ sont pleins de ce genre de "fonctionnalités", voici la GRANDE diaporama: http://www.slideshare.net/olvemaudal/deep-c Donc, si vous voulez voir de plus en plus semblables "caractéristiques", de comprendre ce qui est sous le capot et comment il fonctionne il suffit de regarder ce diaporama - vous ne regretterez pas et je suis sûr que même les plus expérimentés, programmeurs c/c++ peut apprendre beaucoup de ce.

7voto

Yu Hao Points 40603

Dans la fonction A , la variable a n'est pas initialisée, l'impression de sa valeur entraîne un comportement indéfini.

Dans certains compilateurs, les variables a en A et a en B sont à la même adresse, elles peuvent donc imprimer 5 , mais encore une fois, vous ne pouvez pas compter sur un comportement indéfini.

7voto

Gangadhar Points 5474

Compiler votre code avec gcc -Wall filename.c , Vous verrez que ces mises en garde.

In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]

In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]  

Dans c l'Impression variable non initialisée Conduit à un comportement Indéfini.

Section 6.7.8 Initialisation du standard C99 dit

Si un objet qui a automatique de la durée de stockage n'est pas initialisé explicitement, sa valeur est indéterminée. Si un objet statique de la durée de stockage n'est pas initialisé explicitement, alors:

- if it has pointer type, it is initialized to a null pointer;
- if it has arithmetic type, it is initialized to (positive or unsigned) zero;
- if it is an aggregate, every member is initialized (recursively) according to these rules;
- if it is a union, the first named member is initialized (recursively) according to these rules.

Edit1

@Jonathon Reinhart Si vous désactivez l'optimisation en Utilisant -O pavillon gcc-O0 alors vous pourriez obtenir de sortie 5.

Mais ce n'est pas du tout une bonne idée de ne plus jamais l'utiliser dans le code de production.

-Wuninitialized c'est l'un de la valeur de l'avertissement que Vous devriez considérer ce que Vous devriez ne pas désactiver ou d'ignorer cet avertissement qui entraîne des dégâts considérables dans la production comme provoquant des plantages lors de l'exécution des démons.


Edit2

Profonde C diapositives explique Pourquoi le résultat est 5/ordures.L'ajout de cette information à partir de ces diapositives avec des modifications mineures à apporter cette réponse peu plus efficace.

Cas 1: sans optimisation

$ gcc -O0 file.c && ./a.out  
5

Peut-être ce compilateur dispose d'une piscine de les variables nommées qu'il réutilise. Par exemple la variable a été utilisée et rejetée en B(), puis lors de l' A() besoin d'un entier les noms a il obtenir l' la variable d'obtenir la même mémoire emplacement. Si vous renommez la variable en B() de, dire b,, alors je ne pense pas que vous obtiendrez 5.

Cas 2: avec optimisation

Beaucoup de choses peut se produire lorsque l'optimiseur de coups de pied dans. Dans ce cas, je suppose que l'appel à l' B() peut être sautée il n'a pas d'effets secondaires. Aussi, je ne serais pas surpris si l' A() est incorporé en main(), c'est à dire pas d'appel de fonction. (Mais depuis A () a linker visibilité de l'objet code pour la fonction doit toujours être créé juste au cas où un autre objet fichier veut lien avec la fonction). De toute façon, je soupçonne la valeur imprimée sera autre chose si vous optimiser le code.

gcc -O file.c && ./a.out
1606415608  

Des ordures!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X