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

时间:2014-05-09 09:40:51

标签: c callstack method-call

我的C代码存在堆栈消耗问题,我无法弄清楚究竟发生了什么。问题已经开始触发堆栈溢出崩溃(与我的程序中没有使用的递归无关)。我怀疑是因为:
- 我的所有功能至少需要一个自制的调试接口(我用来简化调试的结构),它非常大(~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;
}

使用gcc 4.7.2(XP上的MinGW)和选项-fstack-usage会产生以下奇怪的结果:

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)对我来说似乎很奇怪,因为它意味着在现实生活中,函数大小(从它产生多少次调用的意义上)将受到堆栈可用性的限制。我从来没有听说过这样的限制(不像例如在互联网上有很好解释的递归深度限制)。更重要的是,我一直认为应该在所有(子)呼叫返回时恢复堆栈状态。

生成的程序集如下所示:

...
_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选项)

有人可以确认吗?

1 个答案:

答案 0 :(得分:0)

由于空间不足需要切换回答。除了-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'并检查装配输出。