Подтвердить что ты не робот

Различные статические глобальные переменные совместно используют один и тот же адрес памяти

Резюме

У меня есть несколько исходных файлов C, которые все объявляют индивидуально называемые статическими глобальными переменными. Я понимаю, что статическая глобальная переменная в каждом файле должна быть видна только внутри этого файла и не должна иметь внешнюю привязку, но на самом деле я могу видеть, когда отладка того, что одинаково названные переменные имеют один и тот же адрес памяти.

Похоже, что ключевое слово static игнорируется, а глобальные переменные обрабатываются как extern. Почему это?

Пример кода

foo.c:

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

/* Exported functions ----------------------------------*/
void someFooFunc(void) {
    myVar = VALUE_B;
}

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

Отладочные наблюдения

  • Установите точки останова на строке myVar = ... внутри каждой функции.
  • Вызовите someFooFunc, someBarFunc и someBazFunc в этом порядке из основного.
  • Внутри someFooFunc myVar изначально установлено значение VALUE_A, после перехода по строке оно установлено на VALUE_B.
  • Внутри someBarFunc myVar по какой-то причине сначала устанавливается VALUE_B перед тем, как перейти на строку, а не VALUE_A, как я ожидал бы, указав, что компоновщик может объединить отдельные глобальные переменные, основанные на них, идентичное имя.
  • То же самое относится к someBazFunc, когда он вызывается.
  • Если я использую отладчик для оценки значения &myVar, когда на каждой точке останова указывается тот же адрес.

Инструменты и флаги

Инструментальная цепочка: GNU ARM GCC (6.2 2016q4)

Параметры компилятора:

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"

Параметры компоновщика:

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 ...
4b9b3361

Ответ 1

ПРИМЕЧАНИЕ. Я понимаю, что целевая платформа OP - ARM, но, тем не менее, я все еще отправляю ответ в терминах x86. Причина в том, что у меня нет свободного доступа к ARM, в то время как вопрос не ограничивается конкретной архитектурой.

Вот простой стенд. Обратите внимание, что я использую int вместо пользовательского enum typedef, так как он вообще не имеет значения.

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

Я компилирую его на x86_64 Ubuntu 14.04 с GCC 4.8.4:

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

Получение таких результатов эффективно означает, что переменные myVar в foo.c и bar.c различны. Если вы посмотрите на разборку (на 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   

Вы можете видеть, что фактические адреса статических переменных в разных модулях действительно разные: 0x601040 для foo.c и 0x601044 для bar.c. Однако они связаны с одним символом _ZL5myVar, который действительно закручивает логику GDB.

Вы можете дважды проверить это с помощью objdump -t ./a.out:

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

Опять же, разные адреса, одни и те же символы. Как GDB разрешит этот конфликт, он зависит от реализации.

Я твердо верю, что это ваш случай. Однако, чтобы быть уверенным, вы можете попробовать эти шаги в своей среде.

Ответ 2

so.s делает компоновщика счастливым

.globl _start
_start: b _start

one.c

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

two.c

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

three.c

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

сначала, если вы оптимизируете, тогда это полностью мертвый код, и вы не должны ожидать увидеть какую-либо из этих переменных. Функции не являются статическими, поэтому они не исчезают:

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

Если вы не оптимизируете, то

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

создано три hello-переменных (вы должны заметить, что нет причин запуска отладчика, на все это можно ответить, просто изучив вывод компилятора и компоновщика, отладчик просто мешает)

 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

каждая функция обращается к своей отдельной версии статического глобального. Они не объединены в один общий глобальный.

Ответ 3

Ответы до сих пор показали, что он должен работать как написанный, но фактический ответ есть только в комментариях, поэтому я отправлю его в качестве ответа.

То, что вы видите, - это артефакт отладчика, а не реальная ситуация. По моему опыту, это должно быть вашим первым предположением о любом истинно странном наблюдении в отладчике. Перед продолжением убедитесь, что вы наблюдаете в реальной программе. Например. старомодный отладочный вывод printf.