代码之家  ›  专栏  ›  技术社区  ›  NawaMan

帮助理解C堆栈

  •  1
  • NawaMan  · 技术社区  · 16 年前

    我试图理解C语言中的低级内存管理器,尤其是堆栈。我被告知,当调用函数时,返回地址被推送到堆栈上。然后定位局部变量。

    #include <stdio.h>
    
    void TestStack();
    
    void DoTestStack() {
        char x1 = 1;
        char x2 = 2;
        char x3 = 3;
        char x4 = 4;
        char *x = &x4;
    
        printf("TestStack: %08X\n", (&TestStack));
        printf("\n");
    
        int i;
        x = &x4;
        for(i = 0; i < 32; i++)
            printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));
    
        printf("\n");
        printf("x1: %02X\n", x1);
        printf("x2: %02X\n", x2);
        printf("x3: %02X\n", x3);
    
        printf("DONE!!!\n");
    }
    
    void TestStack() {
        DoTestStack();
    }
    
    void main() {
        TestStack() ;
    }
    

    以下是我的结果:

    TestStack: 08048B49
    
    00: 00000004 : 00000004
    01: 00000003 : FFFFFFBF
    02: 00000002 : FFFFFFAC
    03: 00000001 : FFFFFFED
    04: 00000004 : 0000001C
    05: FFFFFFC3 : 00000000
    06: FFFFFFB9 : 00000000
    07: 00000000 : 00000000
    08: FFFFFFF4 : 00000008
    09: FFFFFFBF : 00000000
    10: FFFFFFB9 : FFFFFF90
    11: 00000000 : FFFFFFBD
    12: 00000038 : 00000020
    13: FFFFFFED : 00000000
    14: FFFFFFAC : 00000000
    15: FFFFFFBF : 00000000
    16: 00000054 : 00000000
    17: FFFFFF8B : 00000000
    18: 00000004 : FFFFFFFF
    19: 00000008 : 00000000
    20: 00000045 : 00000008
    21: 00000073 : 00000000
    22: FFFFFFA7 : 00000000
    23: 00000000 : 00000000
    24: 00000020 : 00000017
    25: FFFFFFBD : 00000008
    26: FFFFFF90 : 00000004
    27: 00000000 : FFFFFF8C
    28: 00000048 : FFFFFFCF
    29: FFFFFFED : 00000008
    30: FFFFFFAC : 00000004
    31: FFFFFFBF : FFFFFF8A
    
    x1: 01
    x2: 02
    x3: 03
    DONE!!!
    

    无论如何,我在Ubuntu 9.10上。

    3 回复  |  直到 16 年前
        1
  •  6
  •   Martin B    16 年前

    您正在查看单个字符,然后将它们转换为32位整数,这让您感到困惑。返回地址位于这四行中的最低有效字节中:

    16: 00000054 : 00000000
    17: FFFFFF8B : 00000000
    18: 00000004 : FFFFFFFF
    19: 00000008 : 00000000
    

    即,您的回信地址是0x08048b54。

    请尝试以下方法:

    uint32_t *x;
    x = (uint32_t *)&x4;
    for(i = 0; i < 32; i++)
        printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));
    
        2
  •  4
  •   Greg Hewgill    16 年前

        printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));
    

    这个 x 指针是一个字符指针,它在解引用后被提升为带符号扩展名的整数,这将导致所有输出值都是000000 xx或ffffff xx,具体取决于位7的值。

    int 指向扫描堆栈值的指针,而不是 char 指针。

        3
  •  1
  •   RandomNickName42    16 年前

    如果您想知道这一切是如何工作的,最好是在调试器中运行应用程序。真的很难弄清楚到底发生了什么。

    任何调试器都可以,但是我最了解windbg,所以这里有一些关于如何开始使用稍微修改过的代码版本的指针,就像Greg建议的那样,您需要一个int*。

     char *x = &x4;
    

    致:

     int *x = &i;
    

    删除:

     x = &x4;
    

    移动(第一个变量,在x1之前):

     int i;
    

    新的更改(更容易阅读,并且与以前一样(x-i)是随机的/当前不在范围值中):

    printf("%02d: %08x : %08x\n", i, x + i, *(x + i));   
    

    在windbg中,启动调用、内存、线程和命令窗口,以便您可以看到它们。 用编译器编译你的C代码,我使用MSVC(你可以免费试用),用“Visual Studio 20##命令提示符”和“cl/Zi your.C”编译。加载Windbg(Windows调试工具),按Ctrl+E或使用“打开可执行文件”。

    在命令窗口中,在DoTestStack上放置一个带有“bu DoTestStack”的断点。

    使用“g”命令开始调试。

    输出应该与测试代码相同(在您执行我建议的更改之后),如果您继续点击p,您还会看到在内存窗口中,您可以观察每次执行I更改的值,但是由于printf现在正在打印其他值,您只需在输出上的原始“0”处看到它;)。

    您可以交替使用windbg命令 (注意,i的值是0xb,因为它在for循环的中间)

    0:000> dds i
    0018ff24  0000000b
    0018ff28  02586bf9
    0018ff2c  0018ff24
    0018ff30  0018ff38
    0018ff34  00401118 a!TestStack+0x8 [c:\temp\a.c @ 33]
    0018ff38  0018ff40
    0018ff3c  00401128 a!main+0x8 [c:\temp\a.c @ 35]
    0018ff40  0018ff88
    0018ff44  00401435 a!__tmainCRTStartup+0xf8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 257]
    0018ff48  00000001
    0018ff4c  003c1e48
    

    这与修改后的测试代码相同(无符号支持除外);

    00: 0018ff24 : 00000000
    01: 0018ff28 : 02586bf9
    02: 0018ff2c : 0018ff24
    03: 0018ff30 : 0018ff38
    04: 0018ff34 : 00401118
    05: 0018ff38 : 0018ff40
    06: 0018ff3c : 00401128
    07: 0018ff40 : 0018ff88
    08: 0018ff44 : 00401435
    09: 0018ff48 : 00000001
    10: 0018ff4c : 003c1e48