c - 与顺序调用相关的堆栈消耗问题

标签 c callstack method-call

我的 C 代码存在堆栈消耗问题,我无法弄清楚到底发生了什么。该问题已开始触发堆栈溢出崩溃(与我的程序中未使用的递归无关)。我怀疑是因为:
- 我的所有功能都至少有一个自制的调试接口(interface)(一种我用来简化调试的结构),它非常大(~1 或 2kB)
- 我的一些函数(例如为某些硬件实现初始化序列的函数)太长(即进行了过多的子函数调用)

注意:我的 C 代码用于多个应用程序,一些是用 gcc 编译的,一些是用 visual studio 2010 编译的,一些是用 C++ 顶层编译的。所有人都受到这种高堆栈使用率问题的困扰。

为了演示问题,我编写了以下简单的测试代码:

typedef struct tDbgIf2 {
   int               verbose                          ;                                         // verbose level (0:no verbose)
   int               callLevel                        ;                                         // context depth in function call
   char              lhd[1024]                        ;                                         // configurable line header for log formating
} tDbgIf;                                                                                       // function debug interface

//----------------------------------------------------------------------------------------------
tDbgIf mfunc(tDbgIf i) {
//----------------------------------------------------------------------------------------------
   tDbgIf   v = i;

   v.verbose++;

   return v;
}

//----------------------------------------------------------------------------------------------
tDbgIf test1call(tDbgIf i) {
//----------------------------------------------------------------------------------------------
   tDbgIf   v = i;

   v = mfunc(v);

   return v;
}

//----------------------------------------------------------------------------------------------
tDbgIf test2call(tDbgIf i) {
//----------------------------------------------------------------------------------------------
   tDbgIf   v = i;

   v = mfunc(v);
   v = mfunc(v);

   return v;
}

//----------------------------------------------------------------------------------------------
tDbgIf test(tDbgIf i) {
//----------------------------------------------------------------------------------------------
   tDbgIf   v = i;

   v           = test1call(v);
   v           = test2call(v);

   return v;
}

//----------------------------------------------------------------------------------------------
int main(int argc, char *argv[]) {
//----------------------------------------------------------------------------------------------
   tDbgIf   v;

   v           = test(v);

   return 0;
}

使用带有选项 -fstack-usage 的 gcc 4.7.2(XP 上的 MinGW)给出以下奇怪的结果:

cmd=gcc -fstack-usage -S test.c -o test.exe
test.c:8:8:mfunc        1060    static     
test.c:18:8:test1call   2100    static     
test.c:28:8:test2call   3164    static     
test.c:39:8:test        3168    static     
test.c:50:5:main        2128    static     

如果我理解正确的话:
- 主要堆栈使用 < 测试堆栈使用 => 统计数据不累积
- gcc 认为 test2call 需要比 test1call 更多的堆栈,尽管这两个函数具有相同的输入/输出参数 + 局部变量

我不明白这是为什么?这表明:(1) 函数的堆栈使用量与它进行的子调用的数量成正比 (1) 对我来说似乎很奇怪,因为这意味着在现实生活中,函数大小(就它进行了多少子调用而言)将受到堆栈可用性的限制。我从来没有听说过这样的限制(不像递归深度限制,例如在 Internet 上有很好的解释)。甚至更多,我一直认为堆栈状态应该在所有(子)调用返回时恢复。

生成的程序集如下所示:

...
_test:                             - start of test
LFB3:                              - 
    .cfi_startproc                   - 
    pushl   %ebp                       - return context saved
    .cfi_def_cfa_offset 8            - 
    .cfi_offset 5, -8                - 
    movl    %esp, %ebp                 - new context activated   
    .cfi_def_cfa_register 5          - 
    pushl   %edi                       - 
    pushl   %esi                       - 
    pushl   %ebx                       - 
    subl    $3148, %esp                - stack static alloc (for test)
    .cfi_offset 7, -12               - 
    .cfi_offset 6, -16               - 
    .cfi_offset 3, -20               - 
    leal    -1056(%ebp), %edx          - v <- i (not sure)
    leal    12(%ebp), %ebx             - v <- i (not sure)
    movl    $258, %eax                 - v <- i (not sure)
    movl    %edx, %edi                 - v <- i (not sure)
    movl    %ebx, %esi                 - v <- i (not sure)
    movl    %eax, %ecx                 - v <- i (not sure)
    rep movsl                        - ???
    leal    -1056(%ebp), %eax          - ???
    movl    %eax, -2108(%ebp)          - ???
    leal    4(%esp), %edx              - ???
    leal    -1056(%ebp), %ebx          - ???
    movl    $258, %eax                 - ???
    movl    %edx, %edi                 - ???
    movl    %ebx, %esi                 - ???
    movl    %eax, %ecx                 - ???
    rep movsl                        - 
    movl    -2108(%ebp), %eax          - 
    movl    %eax, (%esp)               - 
    call    _test1call                 - sub call
    leal    -2104(%ebp), %eax          - v <- ans (not sure)
    movl    %eax, -2112(%ebp)          - v <- ans (not sure)
    leal    4(%esp), %edx              - v <- ans (not sure)
    leal    -1056(%ebp), %ebx          - v <- ans (not sure)
    movl    $258, %eax                 - v <- ans (not sure)
    movl    %edx, %edi                 - v <- ans (not sure)
    movl    %ebx, %esi                 - v <- ans (not sure)
    movl    %eax, %ecx                 - v <- ans (not sure)
    rep movsl                        - 
    movl    -2112(%ebp), %eax          - 
    movl    %eax, (%esp)               - 
    call    _test2call                 - sub call
    leal    -1056(%ebp), %edx          - v <- ans (not sure)
    leal    -2104(%ebp), %ebx          - v <- ans (not sure)
    movl    $258, %eax                 - v <- ans (not sure)
    movl    %edx, %edi                 - v <- ans (not sure)
    movl    %ebx, %esi                 - v <- ans (not sure)
    movl    %eax, %ecx                 - v <- ans (not sure)
    rep movsl                        - ans <- v (not sure)
    movl    8(%ebp), %eax              - ans <- v (not sure)
    movl    %eax, %edx                 - ans <- v (not sure)
    leal    -1056(%ebp), %ebx          - ans <- v (not sure)
    movl    $258, %eax                 - ans <- v (not sure)
    movl    %edx, %edi                 - ans <- v (not sure)
    movl    %ebx, %esi                 - ans <- v (not sure)
    movl    %eax, %ecx                 - ans <- v (not sure)
    rep movsl                        -
    movl    8(%ebp), %eax              -
    addl    $3148, %esp                - stack released
    popl    %ebx                       -
    .cfi_restore 3                   -
    popl    %esi                       -
    .cfi_restore 6                   -
    popl    %edi                       -
    .cfi_restore 7                   -
    popl    %ebp                       - context restored
    .cfi_restore 5                   -
    .cfi_def_cfa 4, 4                -
    ret                              -
    .cfi_endproc                     -
LFE3:
    .def    ___main;    .scl    2;  .type   32; .endef
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
...

如果我理解正确的话:
- 子调用返回后堆栈不会立即清除
- (1) 是真的(至少使用默认的 gcc/visual studio otpimization 选项)

有人可以确认吗?

最佳答案

空间不足需要切换回答。除了 -O0(XP gcc 4.8.2 上的 cygwin)之外,我无法重现您所描述的行为:

-O0
sc.c:8:8:mfunc  24      static
sc.c:18:8:test1call     40      static
sc.c:28:8:test2call     64      static
sc.c:39:5:main  80      dynamic,bounded
-O1
sc.c:8:8:mfunc  4       static
sc.c:18:8:test1call     4       static
sc.c:28:8:test2call     4       static
sc.c:39:5:main  80      dynamic,bounded
-O2
sc.c:8:8:mfunc  4       static
sc.c:18:8:test1call     4       static
sc.c:28:8:test2call     4       static
sc.c:39:5:main  16      static
-O3
sc.c:8:8:mfunc  4       static
sc.c:18:8:test1call     4       static
sc.c:28:8:test2call     4       static
sc.c:39:5:main  16      static

至于“(1):函数的堆栈使用将与它进行的子调用的数量成正比”。如果那是真的,我会感到震惊。嵌套调用 foo(x){ f(g(h(x)));} 增加层级(在激活树中)因此堆栈必须(好吧,几乎是正确的)增长,但在顺序调用中 foo(x) { f (X); g(x); h(x);堆栈被重用。标准激活/返回序列(Aho、Lam、Sethi、Ullman:Compilers Chp 7.2)实际上非常小,如果您在非递归调用中用完 1M 堆栈,您必须具有 (a) 大量局部变量,或者(b) 非常长的参数列表,或者 (c) 您正在使用在堆栈上分配临时对象的 C++ 编译器。

'相反,我一直认为堆栈状态应该在所有(子)调用返回时恢复'。在 C 中,调用者清除堆栈。

我猜 (1) 你要么在某种奇怪的(对我来说)架构上,要么 (2) 你设置了一些不寻常的编译器选项(CFLAGS?)。

最好的选择是“gcc -S”并检查程序集输出。

关于c - 与顺序调用相关的堆栈消耗问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23561354/

相关文章:

javascript - 使用 if/else if 函数根据选择中选择的选项执行不同的操作

c - C中二叉树遍历没有输出

c 结构数组,存储字符串及其出现并将其写入文件

c - 如何编写读取用户输入字符串并仅打印非 ‘a-z’ 或 ‘A-Z’ 的 C 程序

c - 使用 write() 将变量的字节写入文件描述符?

go - 函数接收器的地址因方法而异

ms-access - 如何检查调用堆栈

functional-programming - 没有调用堆栈的架构中的尾部调用

function - 如何解释方法调用?

iphone - 一个接一个地执行方法,执行之间有暂停