在 C99 程序中,在(理论上)假设我没有使用可变长度数组,并且我的每个自动变量在整个堆栈中一次只能存在一次(通过禁止循环函数调用和显式递归),如果我总结他们消耗的所有空间,我可以声明这是可能发生的最大堆栈大小吗?
不,因为函数指针.....阅读n1570。
考虑以下代码,其中rand(3)是一些伪随机数生成器(也可能是来自传感器的一些输入):
typedef int foosig(int);
int foo(int x) {
foosig* fptr = (x>rand())?&foo:NULL;
if (fptr)
return (*fptr)(x);
else
return x+rand();
}
一个优化编译器(例如一些最近适当调用的具有足够优化的GCC)将对(*fptr)(x)
. 其他一些编译器不会。
根据您编译该代码的方式,它将使用有界堆栈或可能产生堆栈溢出。使用一些ABI和调用约定,参数和结果都可以通过处理器寄存器,并且不会消耗任何堆栈空间。
尝试调用最近的 GCC(例如在 Linux/x86-64 上,2020 年的一些GCC 10gcc -O2 -fverbose-asm -S foo.c
),然后查看内部foo.s
。将 更改-O2
为-O0
.
观察到,可以使用足够好的 C 编译器和优化器将朴素的递归阶乘函数编译成一些迭代机器代码。实际上,Linux 上的 GCC 10 编译以下代码:
int fact(int n)
{
if (n<1) return 1;
else return n*fact(n-1);
}
asgcc -O3 -fverbose-asm tmp/fact.c -S -o tmp/fact.s
产生以下汇编代码:
.type fact, @function
fact:
.LFB0:
.cfi_startproc
endbr64
# tmp/fact.c:3: if (n<1) return 1;
movl $1, %eax #, <retval>
testl %edi, %edi # n
jle .L1 #,
.p2align 4,,10
.p2align 3
.L2:
imull %edi, %eax # n, <retval>
subl $1, %edi #, n
jne .L2 #,
.L1:
# tmp/fact.c:5: }
ret
.cfi_endproc
.LFE0:
.size fact, .-fact
.ident "GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0"
您可以观察到调用堆栈没有在上面增加。
如果您对 GCC 有严肃且有据可查的论据,请提交错误报告。
顺便说一句,您可以编写自己的GCC 插件,该插件会选择随机应用或不应用这种优化。我相信它仍然符合 C 标准。
上述优化对于许多生成 C 代码的编译器是必不可少的,例如Chicken/Scheme或Bigloo。
一个相关的定理是赖斯定理。另请参阅由CHARIOT项目资助的这份报告草案。
另请参阅Compcert项目。