不同的静态全局变量共享相同的内存地址
Posted
技术标签:
【中文标题】不同的静态全局变量共享相同的内存地址【英文标题】:Different Static Global Variables Share the Same Memory Address 【发布时间】:2017-12-01 19:29:57 【问题描述】:总结
我有几个 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 = ...
行上设置断点。
从 main 开始依次调用 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 ...
【问题讨论】:
这可能是调试器中的一些名称修改问题,导致它欺骗您。不要信任调试器,而是尝试从它们各自的翻译单元中打印变量的地址和值。 嗯,不同模块中变量的相同命名可能会破坏调试器符号解析。考虑查看someFooFunc
、someBarFunc
和 someBazFunc
汇编代码 - 这可能会提示您这些变量实际上共享相同的地址(这不应该是真的)。
为什么编译用C前端,链接用g++?
使您的程序在变量有或没有单独存储时行为会有所不同,并通过运行程序确认输出。也许链接器检测到它可以做它正在做的事情,因为它不会影响程序。
GDB's manual on program variables 描述如何解析特定变量。一个是bar.c::myVar
,另一个是foo.c::myVar
。此外,如果可用,建议使用-gstabs
,并希望您没有带有成员c
的类foo
。
【参考方案1】:
到目前为止的答案已经证明它应该按书面说明工作,但实际答案仅在 cmets 中,因此我将其作为答案发布。
您看到的是调试器工件,而不是真实情况。根据我的经验,这应该是您对调试器中任何真正奇怪的观察的第一次猜测。在继续之前验证观察在实际运行的程序中。例如。一个老式的调试 printf 语句。
【讨论】:
【参考方案2】:注意:我知道 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;
我正在使用 GCC 4.8.4 在 x86_64 Ubuntu 14.04 上编译它:
$ g++ main.c foo.c bar.c
$ ./a.out
3
4
有效地获得这样的结果意味着foo.c
和bar.c
中的myVar
变量是不同的。如果你看反汇编(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 将如何解决这个冲突完全取决于实现。
我坚信这也是你的情况。但是,为了更加确定,您可能需要在您的环境中尝试这些步骤。
【讨论】:
printf
对我来说不容易获得(针对嵌入式目标进行交叉编译),但存储来自someFooFunc
和someBarFunc
的返回值并使用调试器检查它们会给我3
和4
就像你的例子一样,正如你所说,看起来调试器被相同的变量名弄糊涂了。感谢您的帮助!
@helsmore 好吧,我以printf
为例,其余的你自己想办法:) 事实上,printf
只是一个花哨的装饰器,你可能会看到我没有使用它获取任何低级信息。干杯!【参考方案3】:
so.s 让链接器开心
.globl _start
_start: b _start
一个.c
static unsigned int hello = 4;
static unsigned int one = 5;
void fun1 ( void )
hello=5;
one=6;
两个.c
static unsigned int hello = 4;
static unsigned int two = 5;
void fun2 ( void )
hello=5;
two=6;
三个.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
每个函数都在访问自己独立的静态全局版本。它们没有组合成一个共享的全局。
【讨论】:
以上是关于不同的静态全局变量共享相同的内存地址的主要内容,如果未能解决你的问题,请参考以下文章