Onde as variáveis ​​estáticas são armazenadas em C e C ++?

180

Em que segmento (.BSS, .DATA, outro) de um arquivo executável são variáveis ​​estáticas armazenadas para que não tenham colisão de nomes? Por exemplo:


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);
}                              }

Se eu compilar os dois arquivos e o vincular a um main que chama fooTest () e barTest repetidamente, as instruções printf aumentam independentemente. Faz sentido, pois as variáveis ​​foo e bar são locais na unidade de tradução.

Mas onde o armazenamento é alocado?

Para deixar claro, a suposição é que você possui uma cadeia de ferramentas que produziria um arquivo no formato ELF. Portanto, acredito que deve haver algum espaço reservado no arquivo executável para essas variáveis ​​estáticas.
Para fins de discussão, vamos supor que usamos a cadeia de ferramentas do GCC.

Benoit
fonte
1
A maioria das pessoas está dizendo que elas devem ser armazenadas na seção .DATA em vez de responder à sua pergunta: onde exatamente na seção .DATA e como você pode encontrar onde. Vejo que você já marcou uma resposta, então já sabe como encontrá-la?
Lukmac 20/03
porque inicializado e uninitialised são colocados em diferentes seções: linuxjournal.com/article/1059
mhk
1
O armazenamento alocado para suas variáveis ​​globais / estáticas no tempo de execução não tem nada a ver com a resolução de nomes, o que acontece durante o tempo de compilação / link. Depois que o executável foi criado - não há mais nomes.
Valdo
2
Essa questão não tem sentido, sendo construída com base na falsa premissa de que a "colisão de nomes" de símbolos não exportados é algo que pode existir. O fato de não haver uma pergunta legítima pode explicar como algumas das respostas são terríveis. É difícil acreditar que poucas pessoas tenham entendido isso.
underscore_d

Respostas:

131

Para onde suas estatísticas vão depende se elas são inicializadas com zero . dados estáticos inicializados com zero entram em .BSS (bloco iniciado por símbolo) , dados não inicializados com zero entram em .DATA

Don Neufeld
fonte
50
Por "não inicializado com 0", você provavelmente quer dizer "inicializado, mas com algo diferente de 0". Porque não existem dados estáticos "não inicializados" no C / C ++. Tudo estático é zero inicializado por padrão.
AnT
21
@ Don Neufeld: sua resposta não responde à pergunta. Não entendo por que é aceito. Porque o 'foo' e 'bar' não são 0 inicializados. A questão é onde colocar duas variáveis estático / global com o mesmo nome em .bss ou .data
lukmac
Eu usei implementações nas quais dados estáticos que foram explicitamente inicializados com zero foram inseridos .datae dados estáticos sem inicializador .bss.
MM
1
@MM No meu caso, se o membro estático não foi inicializado (inicial implicitamente inicializado com 0) ou inicializado explicitamente com 0, em ambos os casos, ele foi adicionado na seção .bss.
Cbinder
Essas informações são específicas para um determinado tipo de arquivo executável? Presumo que, como você não especificou, ele se aplica pelo menos aos arquivos executáveis ​​ELF e Windows PE, mas e os outros tipos?
Jerry Jeremiah
116

Quando um programa é carregado na memória, ele é organizado em diferentes segmentos. Um dos segmentos é o segmento DATA . O segmento de dados é subdividido em duas partes:

segmento de dados inicializado: todos os dados globais, estáticos e constantes são armazenados aqui.
Segmento de dados não inicializados (BSS): todos os dados não inicializados são armazenados neste segmento.

Aqui está um diagrama para explicar esse conceito:

insira a descrição da imagem aqui


aqui está um link muito bom explicando esses conceitos:

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

karn
fonte
A resposta acima diz que 0 inicializado entra no BSS. 0 inicializado significa não inicializado ou 0 em si? Se isso significa 0 em si, acho que você deve incluí-lo em sua resposta.
Viraj
Os dados constantes não são armazenados no segmento .data, mas no segmento .const da seção de texto.
precisa saber é o seguinte
Em vez disso (" Segmento de dados inicializado : todos os dados globais, estáticos e constantes são armazenados aqui. Segmento de dados não inicializados (BSS) : todos os dados não inicializados são armazenados neste segmento."), Acho que deveria dizer o seguinte: (" Segmento de dados inicializado : Todas as variáveis ​​globais e estáticas que foram inicializadas com um valor diferente de zero e todos os dados constantes são armazenadas aqui Segmento de dados não inicializados (BSS) : todas as variáveis ​​globais e estáticas que NÃO foram inicializadas ou inicializadas para zero, são armazenados neste segmento. ").
Gabriel Staples
Observe também que, até onde eu entendi, "dados inicializados" podem consistir em variáveis e constantes inicializadas . Em um microcontrolador (ex: STM32), as variáveis ​​inicializadas são armazenadas por padrão na memória Flash e copiadas para a RAM na inicialização , e as constantes inicializadas são deixadas e pretendem ser lidas apenas no Flash , juntamente com o texto que contém o programa em si e é deixado apenas
Gabriel Staples
Então, o que estou coletando neste diagrama é que variáveis ​​globais ou estáticas (uma vez que variáveis ​​estáticas agem como variáveis ​​globais em duração) não estão na pilha nem na pilha, mas são alocadas na memória, além dessas duas. Isso está certo? Suponho que eu poderia dar uma olhada em um script vinculador STM32 novamente para estudar mais a alocação de memória.
Gabriel Staples
32

De fato, uma variável é tupla (armazenamento, escopo, tipo, endereço, valor):

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

Escopo local pode significar local para a unidade de tradução (arquivo de origem), a função ou o bloco, dependendo de onde está definido. Para tornar a variável visível para mais de uma função, ela definitivamente precisa estar na área DATA ou BSS (dependendo se foi inicializada explicitamente ou não, respectivamente). Seu escopo é definido de acordo com todas as funções ou funções no arquivo de origem.

yogeesh
fonte
21

O local de armazenamento dos dados dependerá da implementação.

No entanto, o significado de estático é "ligação interna". Portanto, o símbolo é interno à unidade de compilação (foo.c, bar.c) e não pode ser referenciado fora dessa unidade de compilação. Portanto, não pode haver colisões de nomes.

Seb Rose
fonte
não. keyworld estático sobrecarregou significados: nesse caso, estático é modificador de armazenamento, não modificador de ligação.
ugasoft 18/09/08
4
ugasoft: as estáticas fora da função são modificadores de ligação, dentro são modificadores de armazenamento onde não pode haver colisão para começar.
Wnoise 19/09/08
12

na área "global e estática" :)

Existem várias áreas de memória no C ++:

  • amontoar
  • loja grátis
  • pilha
  • global e estático
  • const

Veja aqui uma resposta detalhada à sua pergunta:

A seguir, são apresentadas as principais áreas de memória distintas de um programa C ++. Observe que alguns dos nomes (por exemplo, "heap") não aparecem como tais no rascunho [padrão].

     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
fonte
12

Eu não acredito que haverá uma colisão. O uso de estática no nível do arquivo (funções externas) marca a variável como local para a unidade de compilação atual (arquivo). Ele nunca é visível fora do arquivo atual, portanto, nunca precisa ter um nome que possa ser usado externamente.

O uso de estática dentro de uma função é diferente - a variável é visível apenas para a função (estática ou não), mas seu valor é preservado nas chamadas para essa função.

Com efeito, a estática faz duas coisas diferentes, dependendo de onde está. Nos dois casos, no entanto, a visibilidade da variável é limitada de forma que você pode impedir facilmente conflitos de espaço de nome ao vincular.

Dito isto, acredito que seria armazenado na DATAseção, que tende a ter variáveis ​​inicializadas com valores diferentes de zero. É claro que esse é um detalhe da implementação, não algo exigido pelo padrão - ele se preocupa apenas com o comportamento, e não com o modo como as coisas são feitas ocultamente.

paxdiablo
fonte
1
@ paxdiablo: você mencionou dois tipos de variáveis ​​estáticas. A qual deles se refere este artigo ( en.wikipedia.org/wiki/Data_segment )? O segmento de dados também contém as variáveis ​​globais (que são exatamente de natureza oposta às estáticas). 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, tem a ver com visibilidade. Pode haver coisas armazenadas em um segmento que são locais para uma unidade de compilação, outras totalmente acessíveis. Um exemplo: pense em cada unidade compor contribuindo com um bloco para o segmento DATA. Ele sabe onde está tudo nesse bloco. Ele também publica os endereços dessas coisas no bloco que deseja que outras unidades de compactação tenham acesso. O vinculador pode resolver esses endereços no momento do link.
precisa
11

Como encontrar você mesmo com objdump -Sr

Para realmente entender o que está acontecendo, você deve entender a realocação do vinculador. Se você nunca tocou nisso, considere ler esta postagem primeiro .

Vamos analisar um exemplo do Linux x86-64 ELF para ver por nós mesmos:

#include <stdio.h>

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

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

Ajuntar com:

gcc -ggdb -c main.c

Descompile o código com:

objdump -Sr main.o
  • -S descompila o código com a fonte original misturada
  • -r mostra informações de realocação

Dentro da descompilação f, vemos:

 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

e o .data-0x4diz que irá para o primeiro byte do .datasegmento.

O -0x4existe porque estamos usando o endereçamento relativo do RIP, portanto, %ripna instrução e R_X86_64_PC32.

É necessário porque o RIP aponta para a instrução a seguir , que inicia 4 bytes, após a 00 00 00 00qual é o que será realocado. Expliquei isso com mais detalhes em: https://stackoverflow.com/a/30515926/895245

Então, se modificarmos a fonte i = 1e fizermos a mesma análise, concluiremos que:

  • static int i = 0 continua .bss
  • static int i = 1 continua .data
Ciro Santilli adicionou uma nova foto
fonte
7

É assim que (fácil de entender):

pilha, pilha e dados estáticos

Yousha Aleayoub
fonte
6

Depende da plataforma e compilador que você está usando. Alguns compiladores são armazenados diretamente no segmento de código. As variáveis ​​estáticas são sempre acessíveis apenas à unidade de conversão atual e os nomes não são exportados, portanto, as colisões de nomes de razões nunca ocorrem.

trotterdylan
fonte
5

Os dados declarados em uma unidade de compilação serão enviados para o .BSS ou o .Data dos arquivos. Dados inicializados no BSS, não inicializados no DATA.

A diferença entre dados estáticos e globais ocorre na inclusão de informações de símbolos no arquivo. Os compiladores tendem a incluir as informações do símbolo, mas apenas marcam as informações globais como tais.

O vinculador respeita essas informações. As informações de símbolo para as variáveis ​​estáticas são descartadas ou desconectadas, para que as variáveis ​​estáticas ainda possam ser referenciadas de alguma maneira (com opções de depuração ou símbolo). Em nenhum dos casos, as unidades de compilação podem ser afetadas, pois o vinculador resolve primeiro as referências locais.

itj
fonte
3

Eu tentei com objdump e gdb, aqui está o resultado que eu recebo:

(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.

aqui está o resultado 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

Portanto, suas quatro variáveis ​​estão localizadas no evento de seção de dados com o mesmo nome, mas com deslocamento diferente.

Dan
fonte
Há muito mais do que isso. Mesmo as respostas existentes não estão completas. Só para mencionar outra coisa: os locais de discussão.
Adriano Repetti
2

variável estática armazenada no segmento de dados ou no código, conforme mencionado anteriormente.
Você pode ter certeza de que ele não será alocado na pilha ou na pilha.
Não há risco de colisão, pois a staticpalavra-chave define o escopo da variável como um arquivo ou função; em caso de colisão, existe um compilador / vinculador para avisá-lo.
Um bom exemplo

Ilya
fonte
1

A resposta pode muito bem depender do compilador, então você provavelmente deseja editar sua pergunta (quero dizer, mesmo a noção de segmentos não é obrigatória pela ISO C nem pela ISO C ++). Por exemplo, no Windows, um executável não carrega nomes de símbolos. Um 'foo' seria compensado em 0x100, o outro talvez 0x2B0, e o código de ambas as unidades de tradução é compilado sabendo os desvios para o "foo" deles.

MSalters
fonte
0

os dois serão armazenados de forma independente; no entanto, se você quiser deixar claro para outros desenvolvedores, poderá agrupá-los em espaços para nome.

Robert Gould
fonte
-1

você já sabe que ele armazena em bss (início do bloco por símbolo) também conhecido como segmento de dados não inicializado ou no segmento de dados inicializado.

vamos dar um exemplo simples

void main(void)
{
static int i;
}

a variável estática acima não é inicializada e, portanto, vai para o segmento de dados não inicializados (bss).

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

e, é claro, inicializado por 10, para o segmento de dados inicializado.

Anurag Bhakuni
fonte