Diferentes Variables Globales Estáticas Comparten la Misma Dirección de Memoria


Resumen

Tengo varios archivos fuente C que declaran variables globales estáticas individuales con nombre idéntico. Entiendo que la variable global estática en cada archivo debe ser visible solo dentro de ese archivo y no debe tener un enlace externo aplicado, pero de hecho puedo ver al depurar que las variables con nombre idéntico comparten la misma dirección de memoria.

Es como si la palabra clave static se ignorara y las variables globales se trataran como extern en su lugar. ¿Por qué es esto?

Código de ejemplo

Foo.c:

/* Private variables -----------------------------------*/
static myEnumType myVar = VALUE_A;

/* Exported functions ----------------------------------*/
void someFooFunc(void) {
    myVar = VALUE_B;
}
[23] Bar.c:
/* Private variables -----------------------------------*/
static myEnumType myVar = VALUE_A;

/* Exported functions ----------------------------------*/
void someBarFunc(void) {
    myVar = VALUE_C;
}

Baz.c:

/* Private variables -----------------------------------*/
static myEnumType myVar = VALUE_A;

/* Exported functions ----------------------------------*/
void someBazFunc(void) {
    myVar = VALUE_D;
}

Observaciones de depuración

  1. Establece puntos de interrupción en la línea myVar = ... dentro de cada función.
  2. Llamar someFooFunc, someBarFunc, y someBazFunc en ese orden desde main.
  3. Dentro someFooFunc myVar inicialmente se establece en VALUE_A, después de pasar por encima de la línea se establece en VALUE_B.
  4. Dentro someBarFunc myVar por alguna razón se establece inicialmente en VALUE_B antes pasar por encima de la línea, no VALUE_A como era de esperar, indicando que el enlazador puede haber fusionado las variables globales separadas basadas en que tienen un nombre idéntico.
  5. Lo mismo ocurre con someBazFunc cuando se llama.
  6. Si utilizo el depurador para evaluar el valor de &myVar cuando en cada punto de interrupción se da la misma dirección.

Herramientas y banderas

Cadena de herramientas: GNU ARM GCC (6.2 2016q4)

Opciones del compilador:

arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mlong-calls -O1 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra  -g3 -DDEBUG -DTRACE -DOS_USE_TRACE_ITM -DSTM32L476xx -I"../include" -I"../system/include" -I"../system/include/cmsis" -I"../system/include/stm32l4xx" -I"../system/include/cmsis/device" -I"../foo/inc" -std=gnu11 -MMD -MP -MF"foo/src/foo.d" -MT"foo/src/foo.o" -c -o "foo/src/foo.o" "../foo/src/foo.c"

Opciones del enlazador:

arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -mlong-calls -O1 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra  -g3 -T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"myProj.map" --specs=nano.specs -o ...
Author: iehrlich, 2017-06-28

3 answers

NOTA: Entiendo que la plataforma objetivo de OP es ARM, pero sin embargo sigo publicando una respuesta en términos de x86. La razón es que no tengo backend ARM a mano, mientras que la pregunta no se limita a una arquitectura en particular.

Aquí hay un simple banco de pruebas. Tenga en cuenta que estoy usando int en lugar de custom enum typedef, ya que no debería importar en todo.

Foo.c

static int myVar = 1;

int someFooFunc(void)
{
        myVar += 2;
        return myVar;
}

Bar.c

static int myVar = 1;

int someBarFunc(void)
{
        myVar += 3;
        return myVar;
}

Main.c

#include <stdio.h>

int someFooFunc(void);
int someBarFunc(void);

int main(int argc, char* argv[])
{
        printf("%d\n", someFooFunc());
        printf("%d\n", someBarFunc());
        return 0;
}

Lo estoy compilando en x86_64 Ubuntu 14.04 con GCC 4.8.4:

$ g++ main.c foo.c bar.c
$ ./a.out
3
4

La obtención de tales resultados significa efectivamente que las variables myVar en foo.c y bar.c son diferentes. Si nos fijamos en el desmontaje (por objdump -D ./a.out):

000000000040052d <_Z11someFooFuncv>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       8b 05 09 0b 20 00       mov    0x200b09(%rip),%eax        # 601040 <_ZL5myVar>
  400537:       83 c0 02                add    $0x2,%eax
  40053a:       89 05 00 0b 20 00       mov    %eax,0x200b00(%rip)        # 601040 <_ZL5myVar>
  400540:       8b 05 fa 0a 20 00       mov    0x200afa(%rip),%eax        # 601040 <_ZL5myVar>
  400546:       5d                      pop    %rbp
  400547:       c3                      retq

0000000000400548 <_Z11someBarFuncv>:
  400548:       55                      push   %rbp
  400549:       48 89 e5                mov    %rsp,%rbp
  40054c:       8b 05 f2 0a 20 00       mov    0x200af2(%rip),%eax        # 601044 <_ZL5myVar>
  400552:       83 c0 03                add    $0x3,%eax
  400555:       89 05 e9 0a 20 00       mov    %eax,0x200ae9(%rip)        # 601044 <_ZL5myVar>
  40055b:       8b 05 e3 0a 20 00       mov    0x200ae3(%rip),%eax        # 601044 <_ZL5myVar>
  400561:       5d                      pop    %rbp
  400562:       c3                      retq   

Puede ver que las direcciones reales de las variables estáticas en diferentes módulos son de hecho diferentes: 0x601040 para foo.c y 0x601044 para bar.c. Sin embargo, están asociados con un solo símbolo _ZL5myVar, lo que realmente arruina la lógica del GDB.

Usted puede comprobar que por medio de objdump -t ./a.out:

0000000000601040 l     O .data  0000000000000004              _ZL5myVar
0000000000601044 l     O .data  0000000000000004              _ZL5myVar

Una vez más, direcciones diferentes, mismos símbolos. La forma en que el BGF resolverá este conflicto depende puramente de la implementación.

Creo firmemente que es su caso también. Sin embargo, para estar doble seguro, es posible que desee probar estos pasos en su entorno.

 22
Author: iehrlich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-28 15:47:12

So.s hacer feliz al enlazador

.globl _start
_start: b _start

Uno.c

static unsigned int hello = 4;
static unsigned int one = 5;
void fun1 ( void )
{
    hello=5;
    one=6;
}

Dos.c

static unsigned int hello = 4;
static unsigned int two = 5;
void fun2 ( void )
{
    hello=5;
    two=6;
}

Tres.c

static unsigned int hello = 4;
static unsigned int three = 5;
void fun3 ( void )
{
    hello=5;
    three=6;
}

En primer lugar, si optimiza, entonces esto es código completamente muerto y no debe esperar ver ninguna de estas variables. Las funciones no son estáticas para que no desaparezcan:

Disassembly of section .text:

08000000 <_start>:
 8000000:   eafffffe    b   8000000 <_start>

08000004 <fun1>:
 8000004:   e12fff1e    bx  lr

08000008 <fun2>:
 8000008:   e12fff1e    bx  lr

0800000c <fun3>:
 800000c:   e12fff1e    bx  lr

Si no optimizas entonces

08000000 <_start>:
 8000000:   eafffffe    b   8000000 <_start>

08000004 <fun1>:
 8000004:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
 8000008:   e28db000    add r11, sp, #0
 800000c:   e59f3020    ldr r3, [pc, #32]   ; 8000034 <fun1+0x30>
 8000010:   e3a02005    mov r2, #5
 8000014:   e5832000    str r2, [r3]
 8000018:   e59f3018    ldr r3, [pc, #24]   ; 8000038 <fun1+0x34>
 800001c:   e3a02006    mov r2, #6
 8000020:   e5832000    str r2, [r3]
 8000024:   e1a00000    nop         ; (mov r0, r0)
 8000028:   e28bd000    add sp, r11, #0
 800002c:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
 8000030:   e12fff1e    bx  lr
 8000034:   20000000    andcs   r0, r0, r0
 8000038:   20000004    andcs   r0, r0, r4

0800003c <fun2>:
 800003c:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
 8000040:   e28db000    add r11, sp, #0
 8000044:   e59f3020    ldr r3, [pc, #32]   ; 800006c <fun2+0x30>
 8000048:   e3a02005    mov r2, #5
 800004c:   e5832000    str r2, [r3]
 8000050:   e59f3018    ldr r3, [pc, #24]   ; 8000070 <fun2+0x34>
 8000054:   e3a02006    mov r2, #6
 8000058:   e5832000    str r2, [r3]
 800005c:   e1a00000    nop         ; (mov r0, r0)
 8000060:   e28bd000    add sp, r11, #0
 8000064:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
 8000068:   e12fff1e    bx  lr
 800006c:   20000008    andcs   r0, r0, r8
 8000070:   2000000c    andcs   r0, r0, r12

08000074 <fun3>:
 8000074:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
 8000078:   e28db000    add r11, sp, #0
 800007c:   e59f3020    ldr r3, [pc, #32]   ; 80000a4 <fun3+0x30>
 8000080:   e3a02005    mov r2, #5
 8000084:   e5832000    str r2, [r3]
 8000088:   e59f3018    ldr r3, [pc, #24]   ; 80000a8 <fun3+0x34>
 800008c:   e3a02006    mov r2, #6
 8000090:   e5832000    str r2, [r3]
 8000094:   e1a00000    nop         ; (mov r0, r0)
 8000098:   e28bd000    add sp, r11, #0
 800009c:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
 80000a0:   e12fff1e    bx  lr
 80000a4:   20000010    andcs   r0, r0, r0, lsl r0
 80000a8:   20000014    andcs   r0, r0, r4, lsl r0

Disassembly of section .data:

20000000 <hello>:
20000000:   00000004    andeq   r0, r0, r4

20000004 <one>:
20000004:   00000005    andeq   r0, r0, r5

20000008 <hello>:
20000008:   00000004    andeq   r0, r0, r4

2000000c <two>:
2000000c:   00000005    andeq   r0, r0, r5

20000010 <hello>:
20000010:   00000004    andeq   r0, r0, r4

Hay tres variables hello creadas (ya debería notar que no hay razón para iniciar el depurador, todo esto puede responderse simplemente examinando la salida del compilador y del enlazador, el depurador se interpone)

 800000c:   e59f3020    ldr r3, [pc, #32]   ; 8000034 <fun1+0x30>

 8000034:   20000000    andcs   r0, r0, r0

 8000044:   e59f3020    ldr r3, [pc, #32]   ; 800006c <fun2+0x30>

 800006c:   20000008    andcs   r0, r0, r8

 800007c:   e59f3020    ldr r3, [pc, #32]   ; 80000a4 <fun3+0x30>

 80000a4:   20000010    andcs   r0, r0, r0, lsl r0

20000000 <hello>:
20000000:   00000004    andeq   r0, r0, r4

20000008 <hello>:
20000008:   00000004    andeq   r0, r0, r4

20000010 <hello>:
20000010:   00000004    andeq   r0, r0, r4

Cada función está accediendo a su propia versión separada del global estático. No se combinan en un solo mundo compartido.

 3
Author: old_timer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-28 14:26:40

Las respuestas hasta ahora han demostrado que debería funcionar como está escrito, pero la respuesta real está solo en los comentarios, así que la publicaré como respuesta.

Lo que estás viendo es un artefacto depurador, no la situación real. En mi experiencia, esta debería ser su primera conjetura de cualquier observación verdaderamente extraña dentro del depurador. Verifique la observación en el programa en ejecución real antes de continuar. Por ejemplo, una declaración printf de depuración antigua.

 3
Author: JDługosz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-28 19:39:12