【问题标题】: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;
}

调试观察

  1. 在每个函数内的myVar = ... 行上设置断点。
  2. 从 main 开始依次调用 someFooFuncsomeBarFuncsomeBazFunc
  3. someFooFuncmyVar 内部最初设置为VALUE_A,越过该行后设置为VALUE_B
  4. 内部someBarFunc myVar 出于某种原因在越线之前最初设置为VALUE_B,而不是我所期望的VALUE_A,这表明链接器可能已经合并了基于它们具有的单独全局变量同名。
  5. someBazFunc 在被调用时也是如此。
  6. 如果我使用调试器在每个断点给出相同地址时评估&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 ...

【问题讨论】:

  • 这可能是调试器中的一些名称修改问题,导致它欺骗您。不要信任调试器,而是尝试从它们各自的翻译单元中打印变量的地址和值。
  • 嗯,不同模块中变量的相同命名可能会破坏调试器符号解析。考虑查看 someFooFuncsomeBarFuncsomeBazFunc 汇编代码 - 这可能会提示您这些变量实际上共享相同的地址(这不应该是真的)。​​
  • 为什么编译用C前端,链接用g++?
  • 使您的程序在变量有或没有单独存储时行为会有所不同,并通过运行程序确认输出。也许链接器检测到它可以做它正在做的事情,因为它不会影响程序。
  • GDB's manual on program variables 描述如何解析特定变量。一个是bar.c::myVar,另一个是foo.c::myVar。此外,如果可用,建议使用-gstabs,并希望您没有带有成员c 的类foo

标签: c gcc arm gnu-arm


【解决方案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;
    }
    

    ma​​in.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.cbar.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.c0x601044对应bar.c。但是,它们与单个符号 _ZL5myVar 相关联,这确实搞砸了 GDB 逻辑。

    您可以通过objdump -t ./a.out 再次检查:

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

    再一次,不同的地址,相同的符号。 GDB 将如何解决这个冲突完全取决于实现。

    我坚信这也是你的情况。但是,为了更加确定,您可能需要在您的环境中尝试这些步骤。

    【讨论】:

    • printf 对我来说不容易获得(针对嵌入式目标进行交叉编译),但存储来自someFooFuncsomeBarFunc 的返回值并使用调试器检查它们会给我34 就像你的例子一样,正如你所说,看起来调试器被相同的变量名弄糊涂了。感谢您的帮助!
    • @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
    

    每个函数都在访问自己独立的静态全局版本。它们没有组合成一个共享的全局。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-25
      • 2014-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多