2010-04-02 6 views
3

Aujourd'hui quelque chose d'étrange m'est venu à l'esprit. Lorsque je veux conserver une chaîne de caractères en C (C++) à l'ancienne, sans utiliser l'en-tête de chaîne, je crée simplement un tableau et stocke cette chaîne dans celui-ci. Mais, j'ai lu que toute définition de variable dans C dans la portée locale de la fonction finit par pousser ces valeurs sur la pile. Donc, la chaîne est en réalité 2 * plus grande que nécessaire. Parce que d'abord, les instructions push sont situées en mémoire, mais lorsqu'elles sont exécutées (poussées sur la pile), une autre "copie" de la chaîne est créée. D'abord les instructions push, que l'espace de la pile est utilisé pour une chaîne.Maintien des variables en mémoire, C++

Alors, pourquoi est-ce ainsi? Pourquoi le compilateur ajoute-t-il simplement la chaîne (ou d'autres variables) au programme au lieu de les créer à nouveau une fois exécuté? Oui, je sais que vous ne pouvez pas avoir certaines données dans le bloc de programme, mais cela pourrait juste être attaché à la fin du programme, avec quelques instructions de saut avant. Et que, nous ne ferions que pointer vers ces données? Parce qu'ils sont stockés dans la RAM lorsque le programme est exécuté.

Merci.

+0

Je vous suggère de regarder ce que fait réellement la compilation dans votre débogueur en mode assembleur/source mixte en accédant à la fonction. Il ne génère pas d'instruction push pour chaque caractère, il va probablement simplement mémcpy() la chaîne d'initialisation dans l'espace réservé à la chaîne sur la trame de la pile - de toute façon ce sera une boucle, pas une instruction par caractère! – Clifford

+0

Oui, mais mon exemple était juste. OK, disons par souci de précision, que j'ai un caractère ehm, "string". Pourtant, il est 2 fois en RAM après l'exécution. –

Répondre

0

Si vous avez:

extern void some_function(char * s, int l); 

void do_it(void) { 
    char str[] = "I'm doing it!"; 
    some_function(str, sizeof(str)); 
} 

Cela se transformerait en quelque chose comme (en psudo asm pour un composé processeur):

.data 
local .do_it.str  ; The contents of str are stored in a static variable 
.text ; text is where code lives within the executable or object file 
do_it: 
    subtract (sizeof(str)) from stack_pointer  ; This reserves the space for str, sizeof(str) 
                ; and a pointer to str on the stack 

    copy (sizeof(str)) bytes from .do_it.str to [stack_pointer+0] ; this loads the local variable 
               ; using to the memory at the top of the stack 
               ; This copy can be a function call or inline code. 

    push sizeof(str)   ; push second argument first 
    push stack_pointer+4  ; assuming that we have 4 byte integers, 
          ; this is the memory just on the other side of where we pushed 
          ; sizeof(str), which is where str[0] ended up 

    call some_function 

    add (sizeof(str)+8) to stack_pointer   ; reclaim the memory used by str, sizeof(str), 
                ; and the pointer to str from the stack 
    return 

De cela, vous pouvez voir que votre hypothèse sur la façon dont la La variable locale str est créée n'est pas complètement correcte, mais ce n'est pas nécessairement aussi efficace qu'elle pourrait l'être.

Si vous avez

void do_it(void) { 
    static str[] = "I'm doing it!"; 

le compilateur ne serait pas réserver l'espace sur la pile pour la chaîne, puis le copier sur la pile. Si some_function devait modifier le contenu de str, l'appel suivant (ou concurrent) à do_it (dans le même processus) utiliserait la version modifiée de str.

Si une_fonction avait été déclaré:

extern void some_function(const char * s, int l); 

Ensuite, puisque le compilateur peut voir qu'il n'y a pas d'opérations qui changent str au sein do_it il pourrait aussi se contenter de ne pas faire une copie locale de str sur la empiler même si str n'ont pas été déclarés static.

5

Il y a deux façons de traiter avec des cordes statiques en C et C++:

char string[] = "Contents of the string"; 
char const *string2 = "Contents of another string"; 

Si vous faites ces dans une fonction, la première crée une chaîne sur la pile, environ comme vous l'avez décrit. La seconde crée juste un pointeur vers une chaîne statique qui est intégrée dans l'exécutable, à peu près comme si vous impliquiez ce que vous voulez.

+0

Oui, merci, c'est exactement ce que je voulais savoir. –

+0

Mais encore, pourquoi est la première façon de stocker la chaîne la plus utilisée dans les tutoriels et ainsi de suite? Quel est l'avantage de cette approche? Merci. –

+0

Je ne peux pas dire avec certitude pourquoi les gens l'utilisent quand. Certains le font probablement par ignorance. D'autres le font probablement pour de bonnes raisons, telles que vouloir modifier le contenu de la chaîne. –

1

Une très bonne question en effet. Vous savez que l'utilisation du mot-clé static sur une variable (définition) EDIT: déclaration fait exactement ce que vous avez décrit, n'est-ce pas?

En ce qui concerne les locaux, l'optimisation des performances est la clé. Une variable locale n'est pas accessible en dehors de la portée d'une fonction. Pourquoi alors le compilateur essaie-t-il de conserver la mémoire en dehors de la pile?

1

Ce n'est pas comme ça que ça fonctionne. Rien ne se «pousse», le compilateur réserve simplement de l'espace dans le cadre de la pile. Vous ne pouvez pas renvoyer une telle chaîne à partir de la fonction, vous retournerez un pointeur vers un cadre de pile morte. Tout appel de fonction ultérieur détruira la chaîne.

Renvoyez les chaînes en laissant l'appelant passer un pointeur vers un tampon, ainsi qu'un argument indiquant la taille du tampon afin de ne pas surcharger la fin du tampon lorsque la chaîne est trop longue.