我的 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/