2013-08-01 1 views
1

Pour les chaînes droites C et GCC, pourquoi la chaîne pointée n'est-elle pas corrompue ici?C Portée de chaîne affectée par une pile

#include <stdio.h> 

int main(int argc, char *argv[]) 
{ 
    char* str_ptr = NULL; 

    { 
     //local to this scope-block 
     char str[4]={0}; 
     sprintf(str, "AGH"); 

     str_ptr = str; 
    } 

    printf("str_ptr: %s\n", str_ptr); 

    getchar(); 
    return 0; 
} 

| ---- SORTIE ----- |

str_ptr: AGH 

| -------------------- |

Voici a link au code ci-dessus compilé et exécuté en utilisant un compilateur en ligne. Je comprends que si str était un littéral de chaîne, str serait stocké dans le bss (essentiellement statique), mais sprintf (ing) dans un tampon alloué par pile, je pensais que le tampon de chaîne serait purement stack- basé (et donc l'adresse sans signification après avoir quitté le bloc de portée)? Je comprends qu'il peut prendre des allocations de pile supplémentaires pour écraser la mémoire à l'adresse donnée, mais même en utilisant une fonction récursive jusqu'à un dépassement de pile, je n'ai pas pu corrompre la chaîne pointée par str_ptr.

FYI Je fais mes tests dans un projet VS2008 C, bien que GCC semble avoir le même comportement.

+0

Ce code n'est pas C90. –

Répondre

1

Il est probable que le compilateur effectue une sorte d'optimisations simples, ce qui fait que la chaîne se trouve toujours au même endroit sur la pile. En d'autres termes, le compilateur permet à la pile de se développer pour stocker 'str'. Mais il ne réduit pas la pile dans le cadre de main, car il n'est pas obligé de le faire.

Si vous voulez vraiment voir le résultat de l'enregistrement de l'adresse des variables sur la pile, appelez une fonction.

#include <stdio.h> 

char * str_ptr = NULL; 
void onstack(void) 
{ 
    char str[4] = {0}; 
    sprintf(str,"AGH"); 
    str_ptr = str; 
} 

int main(int argc, char *argv[]) 
{ 

    onstack(); 
    int x = 0x61626364; 
    printf("str_ptr: %s\n", str_ptr); 
    printf("x:%i\n",x); 
    getchar(); 
    return 0; 
} 

Avec gcc -O0 -std=c99 strcorrupt.c Je reçois une sortie aléatoire sur le premier printf. Cela va varier d'une machine à l'autre et de l'architecture à l'architecture.

+0

Alors pouvez-vous me dire la différence entre le bloc de portée locale et le déplacer dans une fonction? – user2640330

+1

Lorsqu'un compilateur génère le code pour quitter une fonction, il doit avoir ramené la pile à sa taille d'origine.Sinon, la fonction appel verrait une pile corrompue. Ceci est connu comme une convention d'appel. Presque toutes les conventions permettent à l'appelant de supposer que la pile n'a pas changé après un appel de fonction. Les informations ne sont laissées sur la pile que si l'appelé a une valeur à renvoyer. Notez que la fonction 'onstack' est déclarée nulle. Il ne place rien sur la pile que l'appelant puisse utiliser. Pour en savoir plus, lisez: http://en.wikipedia.org/wiki/X86_calling_conventions –

+0

Donc, fondamentalement, parce que la "convention d'appel" ne s'applique pas à la portée de la fonction principale, la pile n'a pas besoin d'être restaurée. état d'origine pré-principal (si pre-main était une fonction). Cela équivaut à la chaîne restée intacte au point où le printf se produit? – user2640330

2

Alors que les lézards nasaux sont une partie populaire du folklore C, le code dont le comportement n'est pas défini peut en fait présenter n'importe quel comportement, y compris des variables ressuscitant magiquement dont la durée de vie a expiré. Le fait que le code avec un comportement indéfini puisse sembler "fonctionner" ne devrait être ni surprenant ni une excuse pour négliger de le corriger. Généralement, à moins que vous n'ayez à écrire des compilateurs, il n'est pas très utile d'examiner la nature précise d'un comportement indéfini dans un environnement donné, d'autant plus que cela peut être différent après avoir clignoté.

Dans ce cas particulier, l'explication est simple, mais il s'agit d'un comportement non défini, donc l'explication suivante ne peut pas être invoquée du tout. Il pourrait à tout moment être remplacé par des émissions reptiliennes. De manière générale, les compilateurs C feront de la taille de la pile de chaque fonction une valeur fixe, plutôt que de se dilater et de se contracter lorsque le flux de contrôle entre et sort des blocs internes. À moins que les fonctions appelées ne soient en ligne, leurs trames de pile ne chevaucheront pas le cadre de pile de l'appelant. Ainsi, dans certains compilateurs C avec certains ensembles d'options de compilation et à l'exception de phases particulières de la lune, le tableau de caractères str ne sera pas remplacé par l'appel à printf, même si la durée de vie de la variable a expiré.

+0

Cette réponse serait excellente, si la première phrase était plus proche de: "Alors que les lézards nasaux sont une partie populaire du folklore C, le code qui repose sur un comportement indéfini peut sembler coïncider à travailler." – Sebivor

+0

@undefinedbehaviour: votre objection aux mots "semble ignorer les erreurs évidentes"? Sinon, je ne suis pas sûr de la différence ... – rici

+0

Mon objection concerne les mots "un comportement indéfini peut être n'importe quoi". Cela implique que même un comportement bien défini peut être indéfini; Plutôt effrayant! 'int x = 0;' n'est pas un comportement indéfini, par exemple. "ni une excuse pour ne pas le corriger" pourrait aussi mieux se lire comme "ni une excuse pour négliger de le corriger". – Sebivor

Questions connexes