Où sont stockées les variables statiques en C et C ++?

180

Dans quel segment (.BSS, .DATA, autre) d'un fichier exécutable sont des variables statiques stockées afin qu'elles n'aient pas de collision de noms? Par exemple:


foo.c:                         bar.c:
static int foo = 1;            static int foo = 10;
void fooTest() {               void barTest() {
  static int bar = 2;            static int bar = 20;
  foo++;                         foo++;
  bar++;                         bar++;
  printf("%d,%d", foo, bar);     printf("%d, %d", foo, bar);
}                              }

Si je compile les deux fichiers et le lie à un main qui appelle fooTest () et barTest à plusieurs reprises, les instructions printf s'incrémentent indépendamment. Cela a du sens puisque les variables foo et bar sont locales à l'unité de traduction.

Mais où est alloué le stockage?

Pour être clair, l'hypothèse est que vous disposez d'une chaîne d'outils qui produirait un fichier au format ELF. Ainsi, je crois qu'il doit y avoir un espace réservé dans le fichier exécutable pour ces variables statiques.
À des fins de discussion, supposons que nous utilisons la chaîne d'outils GCC.

Benoit
la source
1
La plupart des gens vous disent qu'ils devraient être stockés dans la section .DATA au lieu de répondre à votre question: où exactement dans la section .DATA et comment pouvez-vous trouver où. Je vois que vous avez déjà marqué une réponse, donc vous savez déjà comment la trouver?
lukmac
pourquoi initialisé et non initialisé sont placés dans différentes sections: linuxjournal.com/article/1059
mhk
1
Le stockage alloué à vos variables globales / statiques au moment de l'exécution n'a rien à voir avec leur résolution de nom, qui se produit pendant la construction / la liaison. Une fois l'exécutable construit, il n'y a plus de noms.
valdo
2
Cette question n'a pas de sens, étant construite sur la fausse prémisse que la «collision de noms» de symboles non exportés est une chose qui peut exister. Le fait qu'il n'y ait pas de question légitime pourrait expliquer à quel point certaines des réponses sont désastreuses. Il est difficile de croire que si peu de gens l'ont compris.
underscore_d

Réponses:

131

La destination de vos statiques dépend du fait qu'elles sont initialisées à zéro . les données statiques initialisées à zéro vont dans .BSS (Block Started by Symbol) , les données non initialisées à zéro entre dans .DATA

Don Neufeld
la source
50
Par "non-0 initialisé", vous voulez probablement dire "initialisé, mais avec autre chose que 0". Parce qu'il n'y a pas de données statiques "non initialisées" en C / C ++. Tout ce qui est statique est initialisé à zéro par défaut.
Du
21
@Don Neufeld: votre réponse ne répond pas du tout à la question. Je ne comprends pas pourquoi il est accepté. Parce que les deux «foo» et «bar» ne sont pas initialisés à 0. La question est de savoir où placer deux variables statiques / globales avec le même nom dans .bss ou .data
lukmac
J'ai utilisé des implémentations dans lesquelles des données statiques explicitement initialisées à zéro sont entrées .dataet des données statiques sans initialiseur sont entrées .bss.
MM
1
@MM Dans mon cas, que le membre statique soit non initialisé (implicitement initialisé à 0) ou explicitement initialisé à 0, dans les deux cas, il s'est ajouté dans la section .bss.
cbinder
Cette information est-elle spécifique à un certain type de fichier exécutable? Je suppose, puisque vous ne l'avez pas spécifié, que cela s'applique au moins aux fichiers exécutables ELF et Windows PE, mais qu'en est-il des autres types?
Jerry Jeremiah le
116

Lorsqu'un programme est chargé en mémoire, il est organisé en différents segments. L'un des segments est le segment DATA . Le segment de données est en outre sous-divisé en deux parties:

Segment de données initialisé: toutes les données globales, statiques et constantes sont stockées ici.
Segment de données non initialisé (BSS): toutes les données non initialisées sont stockées dans ce segment.

Voici un diagramme pour expliquer ce concept:

entrez la description de l'image ici


voici un très bon lien expliquant ces concepts:

http://www.inf.udec.cl/~leo/teoX.pdf

karn
la source
La réponse ci-dessus indique que 0 initialisé entre dans BSS. Est-ce que 0 initialisé signifie non initialisé ou 0 en soi? Si cela signifie 0 en soi, je pense que vous devriez l'inclure dans votre réponse.
Viraj
Les données constantes ne sont pas stockées dans le segment .data mais dans le segment .const de la section de texte.
user10678
Au lieu de cela (" Segment de données initialisé : Toutes les données globales, statiques et constantes sont stockées ici. Segment de données non initialisé (BSS) : Toutes les données non initialisées sont stockées dans ce segment."), Je pense qu'il devrait dire ceci: (" Segment de données initialisé : toutes les variables globales et statiques qui ont été initialisées à une valeur différente de zéro et toutes les données constantes sont stockées ici. Segment de données non initialisé (BSS) : toutes les variables globales et statiques qui n'ont pas été initialisées ou initialisées. à zéro, sont stockés dans ce segment. ").
Gabriel Staples
Notez également que pour autant que je sache, les «données initialisées» peuvent être constituées de variables et de constantes initialisées . Sur un microcontrôleur (ex: STM32), les variables initialisées sont stockées par défaut dans la mémoire Flash et copiées dans la RAM au démarrage , et les constantes initialisées sont laissées et destinées à être lues à partir de Flash uniquement , avec le texte , qui contient le programme lui-même, et est laissé dans Flash uniquement.
Gabriel Staples
Donc, ce que je retiens de ce diagramme, c'est que les variables qui sont globales ou statiques (puisque les variables statiques agissent comme des variables globales en durée) ne sont ni sur le tas ni dans la pile, mais sont plutôt allouées en mémoire en dehors de ces deux. Est-ce correct? Je suppose que je pourrais jeter un coup d'œil à un script de l'éditeur de liens STM32 à nouveau pour étudier davantage l'allocation de mémoire.
Gabriel Staples le
32

En fait, une variable est un tuple (stockage, portée, type, adresse, valeur):

storage     :   where is it stored, for example data, stack, heap...
scope       :   who can see us, for example global, local...
type        :   what is our type, for example int, int*...
address     :   where are we located
value       :   what is our value

La portée locale peut signifier local pour l'unité de traduction (fichier source), la fonction ou le bloc selon l'endroit où il est défini. Pour rendre la variable visible à plus d'une fonction, elle doit certainement être dans la zone DATA ou BSS (selon qu'elle est initialisée explicitement ou non, respectivement). Il est ensuite étendu en conséquence à toutes les fonctions ou à toutes les fonctions du fichier source.

yogeesh
la source
21

L'emplacement de stockage des données dépendra de la mise en œuvre.

Cependant, le sens de statique est «lien interne». Ainsi, le symbole est interne à l'unité de compilation (foo.c, bar.c) et ne peut pas être référencé en dehors de cette unité de compilation. Donc, il ne peut y avoir aucune collision de noms.

Seb Rose
la source
non. static keyworld a des significations surchargées: dans un tel cas, static est un modificateur de stockage, pas un modificateur de liaison.
ugasoft
4
ugasoft: les statiques en dehors de la fonction sont des modificateurs de liaison, à l'intérieur se trouvent des modificateurs de stockage où il ne peut y avoir aucune collision pour commencer.
wnoise
12

dans la zone "globale et statique" :)

Il existe plusieurs zones de mémoire en C ++:

  • tas
  • magasin gratuit
  • empiler
  • global et statique
  • const

Voir ici pour une réponse détaillée à votre question:

Ce qui suit résume les principales zones de mémoire distinctes d'un programme C ++. Notez que certains des noms (par exemple, "tas") n'apparaissent pas comme tels dans le brouillon [standard].

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.
ugasoft
la source
12

Je ne crois pas qu'il y aura une collision. L'utilisation de statique au niveau du fichier (fonctions externes) marque la variable comme locale à l'unité de compilation actuelle (fichier). Il n'est jamais visible en dehors du fichier actuel et n'a donc jamais à avoir un nom pouvant être utilisé en externe.

L'utilisation de statique à l' intérieur d' une fonction est différente - la variable n'est visible que par la fonction (qu'elle soit statique ou non), c'est juste sa valeur est préservée à travers les appels à cette fonction.

En effet, la statique fait deux choses différentes selon l'endroit où elle se trouve. Dans les deux cas cependant, la visibilité de la variable est limitée de telle manière que vous pouvez facilement éviter les conflits d'espace de noms lors de la liaison.

Cela dit, je pense qu'il serait stocké dans la DATAsection, qui a tendance à avoir des variables initialisées à des valeurs autres que zéro. Il s'agit, bien sûr, d'un détail de mise en œuvre, pas de quelque chose exigé par la norme - il ne se soucie que du comportement, pas de la façon dont les choses sont faites sous les couvertures.

paxdiablo
la source
1
@paxdiablo: vous avez évoqué deux types de variables statiques. À lequel d'entre eux cet article ( en.wikipedia.org/wiki/Data_segment ) fait-il référence? Le segment de données contient également les variables globales (qui sont exactement de nature opposée aux variables statiques). So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer
@eSKay, cela a à voir avec la visibilité. Il peut y avoir des choses stockées dans un segment qui sont locales à une unité de compilation, d'autres qui sont entièrement accessibles. Un exemple: pensez à chaque unité comp contribuant un bloc au segment DATA. Il sait où tout est dans ce bloc. Il publie également les adresses de ces éléments dans le bloc auquel il souhaite que d'autres unités de composition aient accès. L'éditeur de liens peut résoudre ces adresses au moment de la liaison.
paxdiablo
11

Comment le trouver vous-même avec objdump -Sr

Pour comprendre réellement ce qui se passe, vous devez comprendre la relocalisation de l'éditeur de liens. Si vous n'avez jamais touché à cela, pensez à lire d'abord cet article .

Analysons un exemple ELF Linux x86-64 pour le voir nous-mêmes:

#include <stdio.h>

int f() {
    static int i = 1;
    i++;
    return i;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", f());
    return 0;
}

Compiler avec:

gcc -ggdb -c main.c

Décompilez le code avec:

objdump -Sr main.o
  • -S décompile le code avec la source originale entremêlée
  • -r affiche les informations de déménagement

À l'intérieur de la décompilation de fnous voyons:

 static int i = 1;
 i++;
4:  8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <f+0xa>
        6: R_X86_64_PC32    .data-0x4

et le .data-0x4dit qu'il ira au premier octet du .datasegment.

Le -0x4est là parce que nous utilisons l'adressage relatif RIP, donc le %ripdans l'instruction et R_X86_64_PC32.

Il est nécessaire car RIP pointe vers l' instruction suivante , qui commence 4 octets après 00 00 00 00quoi est ce qui sera déplacé. J'ai expliqué cela plus en détail sur: https://stackoverflow.com/a/30515926/895245

Ensuite, si nous modifions la source i = 1et faisons la même analyse, nous concluons que:

  • static int i = 0 continue .bss
  • static int i = 1 continue .data
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
7

Voici comment (facile à comprendre):

pile, tas et données statiques

Yousha Aleayoub
la source
6

Cela dépend de la plate-forme et du compilateur que vous utilisez. Certains compilateurs stockent directement dans le segment de code. Les variables statiques ne sont toujours accessibles qu'à l'unité de traduction actuelle et les noms ne sont pas exportés, ce qui explique pourquoi les collisions de noms ne se produisent jamais.

trotteur
la source
5

Les données déclarées dans une unité de compilation iront dans le .BSS ou le .Data de la sortie de ces fichiers. Données initialisées dans BSS, non activées dans DATA.

La différence entre les données statiques et globales réside dans l'inclusion des informations de symbole dans le fichier. Les compilateurs ont tendance à inclure les informations de symbole mais ne marquent que les informations globales en tant que telles.

L'éditeur de liens respecte ces informations. Les informations de symbole pour les variables statiques sont soit ignorées, soit mutilées afin que les variables statiques puissent toujours être référencées d'une manière ou d'une autre (avec des options de débogage ou de symbole). Dans les deux cas, les unités de compilation ne peuvent être affectées lorsque l'éditeur de liens résout d'abord les références locales.

itj
la source
3

Je l'ai essayé avec objdump et gdb, voici le résultat que j'obtiens:

(gdb) disas fooTest
Dump of assembler code for function fooTest:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: mov    0x200b09(%rip),%eax        # 0x601040 <foo>
   0x0000000000400537 <+10>:    add    $0x1,%eax
   0x000000000040053a <+13>:    mov    %eax,0x200b00(%rip)        # 0x601040 <foo>
   0x0000000000400540 <+19>:    mov    0x200afe(%rip),%eax        # 0x601044 <bar.2180>
   0x0000000000400546 <+25>:    add    $0x1,%eax
   0x0000000000400549 <+28>:    mov    %eax,0x200af5(%rip)        # 0x601044 <bar.2180>
   0x000000000040054f <+34>:    mov    0x200aef(%rip),%edx        # 0x601044 <bar.2180>
   0x0000000000400555 <+40>:    mov    0x200ae5(%rip),%eax        # 0x601040 <foo>
   0x000000000040055b <+46>:    mov    %eax,%esi
   0x000000000040055d <+48>:    mov    $0x400654,%edi
   0x0000000000400562 <+53>:    mov    $0x0,%eax
   0x0000000000400567 <+58>:    callq  0x400410 <printf@plt>
   0x000000000040056c <+63>:    pop    %rbp
   0x000000000040056d <+64>:    retq   
End of assembler dump.

(gdb) disas barTest
Dump of assembler code for function barTest:
   0x000000000040056e <+0>: push   %rbp
   0x000000000040056f <+1>: mov    %rsp,%rbp
   0x0000000000400572 <+4>: mov    0x200ad0(%rip),%eax        # 0x601048 <foo>
   0x0000000000400578 <+10>:    add    $0x1,%eax
   0x000000000040057b <+13>:    mov    %eax,0x200ac7(%rip)        # 0x601048 <foo>
   0x0000000000400581 <+19>:    mov    0x200ac5(%rip),%eax        # 0x60104c <bar.2180>
   0x0000000000400587 <+25>:    add    $0x1,%eax
   0x000000000040058a <+28>:    mov    %eax,0x200abc(%rip)        # 0x60104c <bar.2180>
   0x0000000000400590 <+34>:    mov    0x200ab6(%rip),%edx        # 0x60104c <bar.2180>
   0x0000000000400596 <+40>:    mov    0x200aac(%rip),%eax        # 0x601048 <foo>
   0x000000000040059c <+46>:    mov    %eax,%esi
   0x000000000040059e <+48>:    mov    $0x40065c,%edi
   0x00000000004005a3 <+53>:    mov    $0x0,%eax
   0x00000000004005a8 <+58>:    callq  0x400410 <printf@plt>
   0x00000000004005ad <+63>:    pop    %rbp
   0x00000000004005ae <+64>:    retq   
End of assembler dump.

voici le résultat objdump

Disassembly of section .data:

0000000000601030 <__data_start>:
    ...

0000000000601038 <__dso_handle>:
    ...

0000000000601040 <foo>:
  601040:   01 00                   add    %eax,(%rax)
    ...

0000000000601044 <bar.2180>:
  601044:   02 00                   add    (%rax),%al
    ...

0000000000601048 <foo>:
  601048:   0a 00                   or     (%rax),%al
    ...

000000000060104c <bar.2180>:
  60104c:   14 00                   adc    $0x0,%al

Donc, c'est-à-dire, vos quatre variables sont situées dans la section de données événement du même nom, mais avec un décalage différent.

Dan
la source
Il y a bien plus que cela. Même les réponses existantes ne sont pas complètes. Pour mentionner autre chose: les sections locales du fil.
Adriano Repetti
2

variable statique stockée dans un segment de données ou un segment de code comme mentionné précédemment.
Vous pouvez être sûr qu'il ne sera pas alloué sur la pile ou le tas.
Il n'y a pas de risque de collision puisque le staticmot-clé définit la portée de la variable comme un fichier ou une fonction, en cas de collision il y a un compilateur / éditeur de liens pour vous avertir.
Un bel exemple

Il y a
la source
1

La réponse pourrait très bien dépendre du compilateur, donc vous voudrez probablement éditer votre question (je veux dire, même la notion de segments n'est pas mandatée par ISO C ni ISO C ++). Par exemple, sous Windows, un exécutable ne porte pas de noms de symboles. Un «foo» serait le décalage 0x100, l'autre peut-être 0x2B0, et le code des deux unités de traduction est compilé en connaissant les décalages pour «leur» toto.

MSalters
la source
0

ils seront tous les deux stockés indépendamment, mais si vous voulez le faire comprendre aux autres développeurs, vous voudrez peut-être les envelopper dans des espaces de noms.

Robert Gould
la source
-1

vous savez déjà qu'il stocke dans bss (début de bloc par symbole) également appelé segment de données non initialisé ou dans segment de données initialisé.

prenons un exemple simple

void main(void)
{
static int i;
}

la variable statique ci-dessus n'est pas initialisée, elle va donc au segment de données non initialisé (bss).

void main(void)
{
static int i=10;
}

et bien sûr, il a été initialisé par 10 pour qu'il passe au segment de données initialisé.

Anurag Bhakuni
la source