Различные статические глобальные переменные совместно используют один и тот же адрес памяти
Резюме
У меня есть несколько исходных файлов 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 ...
Ответы
Ответ 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.