¿Dónde se almacenan las variables estáticas en C y C ++?


180

¿En qué segmento (.BSS, .DATA, otro) de un archivo ejecutable se almacenan las variables estáticas para que no tengan colisión de nombres? Por ejemplo:


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 compilo ambos archivos y los vinculo a un main que llama a fooTest () y barTest repetidamente, las declaraciones printf se incrementan independientemente. Tiene sentido ya que las variables foo y bar son locales de la unidad de traducción.

Pero, ¿dónde se asigna el almacenamiento?

Para ser claros, se supone que tiene una cadena de herramientas que generaría un archivo en formato ELF. Por lo tanto, yo creo que no tiene que ser un espacio reservado en el archivo ejecutable para aquellas variables estáticas.
Para fines de discusión, supongamos que utilizamos la cadena de herramientas GCC.


1
La mayoría de las personas le dicen que deben almacenarse en la sección .DATA en lugar de responder a su pregunta: dónde exactamente en la sección .DATA y cómo puede encontrar dónde. Veo que ya marcó una respuesta, ¿ya sabe cómo encontrarla?
lukmac

por qué inicializado y no inicializado se colocan en diferentes secciones: linuxjournal.com/article/1059
mhk

1
El almacenamiento asignado a sus variables globales / estáticas en tiempo de ejecución no tiene nada que ver con su resolución de nombre, que ocurre durante el tiempo de compilación / enlace. Una vez que se ha creado el ejecutable, no hay más nombres.
valdo

2
Esta pregunta no tiene sentido, basándose en la falsa premisa de que "colisión de nombres" de símbolos no exportados es algo que puede existir. El hecho de que no haya una pregunta legítima podría explicar cuán graves son algunas de las respuestas. Es difícil de creer que tan poca gente tenga esto.
underscore_d

Respuestas:


131

El destino de sus estadísticas depende de si están inicializadas en cero . los datos estáticos con inicialización cero entran en .BSS (bloque iniciado por símbolo) , los datos con inicialización distinta de cero ingresan .DATA


50
Por "no 0 inicializado" probablemente quiere decir "inicializado, pero con algo distinto de 0". Porque no hay datos estáticos "no inicializados" en C / C ++. Todo lo estático está inicializado en cero de forma predeterminada.
ANT

21
@Don Neufeld: su respuesta no responde a la pregunta en absoluto. No entiendo por qué se acepta. Porque tanto el 'foo' como el 'bar' no están inicializados en 0. La pregunta es dónde colocar dos variables estáticas / globales con el mismo nombre en .bss o .data
lukmac

He usado implementaciones donde ingresaron datos estáticos que fueron explícitamente inicializados en cero y entraron .datadatos estáticos sin inicializador .bss.
MM

1
@MM En mi caso, si el miembro estático no está inicializado (implícitamente inicializado a 0) o explícitamente inicializado a 0, en ambos casos se sumó en la sección .bss.
cbinder

¿Es esta información específica de un determinado tipo de archivo ejecutable? Supongo, ya que no especificó, que se aplica al menos a los archivos ejecutables ELF y Windows PE, pero ¿qué pasa con otros tipos?
Jerry Jeremiah

116

Cuando un programa se carga en la memoria, se organiza en diferentes segmentos. Uno del segmento es el segmento de DATOS . El segmento de datos se subdivide en dos partes:

segmento de datos inicializados: aquí se almacenan todos los datos globales, estáticos y constantes.
Segmento de datos sin inicializar (BSS): todos los datos sin inicializar se almacenan en este segmento.

Aquí hay un diagrama para explicar este concepto:

ingrese la descripción de la imagen aquí


Aquí hay un muy buen enlace que explica estos conceptos:

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


La respuesta anterior dice que 0 inicializado entra en BSS. ¿0 inicializado significa no inicializado o 0 per se? Si significa 0 per se, creo que deberías incluirlo en tu respuesta.
Viraj

Los datos constantes no se almacenan en el segmento .data sino dentro del segmento .const de la sección de texto.
user10678

En lugar de esto (" Segmento de datos inicializados : todos los datos globales, estáticos y constantes se almacenan aquí. Segmento de datos no inicializados (BSS) : Todos los datos no inicializados se almacenan en este segmento"), creo que debería decir esto: (" Segmento de datos inicializados : todas las variables globales y estáticas que se inicializaron a un valor distinto de cero, y todos los datos constantes, se almacenan aquí. Segmento de datos no inicializados (BSS) : todas las variables globales y estáticas que NO se inicializaron o inicializaron a cero, se almacenan en este segmento ").
Gabriel Staples el

También tenga en cuenta que hasta donde yo entiendo, los "datos inicializados" pueden consistir en variables y constantes inicializadas . En un microcontrolador (p. Ej., STM32), las variables inicializadas se almacenan de forma predeterminada en la memoria Flash y se copian en la RAM al inicio , y las constantes inicializadas se dejan en Flash y solo se deben leer , junto con el texto , que contiene el programa en sí, y se deja solo
Gabriel Staples el

Entonces, lo que estoy recopilando de este diagrama es que las variables que son globales o estáticas (ya que las variables estáticas actúan como variables globales en duración) no están en el montón ni en la pila, sino que están asignadas en la memoria, aparte de ambas. ¿Está bien? Supongo que podría echar un vistazo a un script de enlace STM32 nuevamente para estudiar más la asignación de memoria.
Gabriel Staples

32

De hecho, una variable es tupla (almacenamiento, alcance, tipo, dirección, 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

El alcance local podría significar local para la unidad de traducción (archivo fuente), la función o el bloque dependiendo de dónde esté definido. Para hacer que la variable sea visible para más de una función, definitivamente tiene que estar en el área de DATOS o BSS (dependiendo de si se inicializó explícitamente o no, respectivamente). Luego, se ajusta a todas las funciones o funciones dentro del archivo fuente.


21

La ubicación de almacenamiento de los datos dependerá de la implementación.

Sin embargo, el significado de estático es "enlace interno". Por lo tanto, el símbolo es interno a la unidad de compilación (foo.c, bar.c) y no puede ser referenciado fuera de esa unidad de compilación. Por lo tanto, no puede haber colisiones de nombres.


No. static keyworld ha sobrecargado significados: en tal caso, static es modificador de almacenamiento, no modificador de enlace.
ugasoft

44
ugasoft: la estática fuera de la función son modificadores de vinculación, dentro hay modificadores de almacenamiento donde no puede haber colisión para empezar.
wnoise

12

en el área "global y estática" :)

Hay varias áreas de memoria en C ++:

  • montón
  • tienda libre
  • apilar
  • global y estático
  • const

Consulte aquí para obtener una respuesta detallada a su pregunta:

A continuación se resumen las principales áreas de memoria distintas de un programa C ++. Tenga en cuenta que algunos de los nombres (por ejemplo, "montón") no aparecen como tales en el borrador [estándar].

     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.

12

No creo que haya una colisión. El uso de estática a nivel de archivo (funciones externas) marca la variable como local a la unidad de compilación actual (archivo). Nunca es visible fuera del archivo actual, por lo que nunca debe tener un nombre que pueda usarse externamente.

El uso de estática dentro de una función es diferente: la variable solo es visible para la función (ya sea estática o no), solo se conserva su valor en las llamadas a esa función.

En efecto, la estática hace dos cosas diferentes dependiendo de dónde se encuentre. Sin embargo, en ambos casos, la visibilidad variable es limitada de tal manera que puede evitar fácilmente conflictos de espacio de nombres al vincular.

Dicho esto, creo que se almacenaría en la DATAsección, que tiende a tener variables que se inicializan a valores distintos de cero. Esto es, por supuesto, un detalle de implementación, no algo ordenado por el estándar: solo se preocupa por el comportamiento, no por cómo se hacen las cosas de manera encubierta.


1
@paxdiablo: ha mencionado dos tipos de variables estáticas. ¿A cuál de ellos se refiere este artículo ( en.wikipedia.org/wiki/Data_segment )? El segmento de datos también contiene las variables globales (que son exactamente opuestas en naturaleza a las 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, tiene que ver con la visibilidad. Puede haber cosas almacenadas en un segmento que son locales a una unidad de compilación, otras que son totalmente accesibles. Un ejemplo: piense en cada unidad comp contribuyendo con un bloque al segmento DATA. Sabe dónde está todo en ese bloque. También publica las direcciones de esas cosas en el bloque al que desea que otras unidades comp tengan acceso. El enlazador puede resolver esas direcciones en el momento del enlace.
paxdiablo

11

Cómo encontrarlo tú mismo con objdump -Sr

Para comprender realmente lo que está sucediendo, debe comprender la reubicación del enlazador. Si nunca has tocado eso, considera leer esta publicación primero .

Analicemos un ejemplo ELF Linux x86-64 para verlo nosotros mismos:

#include <stdio.h>

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

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

Compilar con:

gcc -ggdb -c main.c

Descompilar el código con:

objdump -Sr main.o
  • -S descompila el código con la fuente original entremezclada
  • -r muestra información de reubicación

Dentro de la descompilación de fvemos:

 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

y .data-0x4dice que irá al primer byte del .datasegmento.

El -0x4está allí porque estamos usando el direccionamiento relativo RIP, por lo tanto, %ripen la instrucción y R_X86_64_PC32.

Es necesario porque RIP apunta a la siguiente instrucción, que comienza 4 bytes y luego 00 00 00 00se reubicará. He explicado esto con más detalle en: https://stackoverflow.com/a/30515926/895245

Luego, si modificamos la fuente i = 1y hacemos el mismo análisis, concluimos que:

  • static int i = 0 sucede .bss
  • static int i = 1 sucede .data


6

Depende de la plataforma y el compilador que esté utilizando. Algunos compiladores se almacenan directamente en el segmento de código. Las variables estáticas siempre son accesibles solo para la unidad de traducción actual y los nombres no se exportan, por lo que nunca se producen colisiones de nombres.


5

Los datos declarados en una unidad de compilación irán a .BSS o .Data de la salida de esos archivos. Datos inicializados en BSS, no inicializados en DATA.

La diferencia entre los datos estáticos y globales viene en la inclusión de información de símbolos en el archivo. Los compiladores tienden a incluir la información del símbolo pero solo marcan la información global como tal.

El vinculador respeta esta información. La información de símbolos para las variables estáticas se descarta o se destruye para que las variables estáticas puedan ser referenciadas de alguna manera (con opciones de depuración o símbolo). En ninguno de los casos, las unidades de compilación pueden verse afectadas ya que el vinculador resuelve primero las referencias locales.


3

Lo probé con objdump y gdb, aquí está el resultado que obtengo:

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

Aquí está el 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

Entonces, es decir, sus cuatro variables se encuentran en la sección de datos del evento con el mismo nombre, pero con un desplazamiento diferente.


Hay mucho, mucho más que eso. Incluso las respuestas existentes no están completas. Solo por mencionar algo más: hilo locales.
Adriano Repetti

2

variable estática almacenada en el segmento de datos o segmento de código como se mencionó anteriormente.
Puede estar seguro de que no se asignará en la pila o el montón.
No hay riesgo de colisión ya que la staticpalabra clave define el alcance de la variable como un archivo o función, en caso de colisión hay un compilador / enlazador para advertirle.
Un buen ejemplo



1

La respuesta bien podría depender del compilador, por lo que probablemente desee editar su pregunta (quiero decir, incluso la noción de segmentos no es obligatoria por ISO C ni ISO C ++). Por ejemplo, en Windows un ejecutable no lleva nombres de símbolos. Un 'foo' se compensaría 0x100, el otro quizás 0x2B0, y el código de ambas unidades de traducción se compila sabiendo las compensaciones para "su" foo.


0

ambos se almacenarán de forma independiente, sin embargo, si desea dejarlo claro a otros desarrolladores, es posible que desee envolverlos en espacios de nombres.


-1

ya sabe que se almacena en bss (inicio de bloque por símbolo) también denominado segmento de datos no inicializado o en segmento de datos inicializado.

tomemos un ejemplo simple

void main(void)
{
static int i;
}

la variable estática anterior no se inicializa, por lo que va al segmento de datos no inicializados (bss).

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

y, por supuesto, se inicializó en 10, por lo que va al segmento de datos inicializado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.