clang在指定-O2时对函数局部变量的优化
在我们将编译器从g++迁移到clang++的过程中,遇到一个问题,有个工具程序只要一运行就会出现core dump问题,并且用gdb调试core文件也无法获得任何有用的堆栈信息。
通过不断尝试,发现只有在clang++使用-O2编译时得到的程序才会发生这个问题,使用clang++ -O0或者g++编译时不会发生问题。
然后我们又尝试将bash的默认stack size调大到1000M后去执行程序,就不会出现core dump。
从stack size这个方向来看,应该是栈空间不够大导致的,但是问题在于在main函数第一个指令执行之前就会出现core,此时还没有申明或者使用任何函数局部变量。据本人目前了解的知识来推测可能有以下两种可能:
- 是不是有超大的__thread类型的变量导致的?
- 某个函数中使用了什么特殊代码导致clang++ -O2优化到main执行前的过程了?
先对第1种猜想进行排查代码后,没有发生有这么大的线程局部变量,先搁置。
然后考虑第2种猜想,此时没有其他好的办法只能从main函数中使用二分法进行排查,从后往前删代码,看看是哪段代码导致的。果然发现一个函数只要出现在main函数中,就会导致问题(该函数只有在特定的一种情况下才会被调用,出core的情况下不会调用到该函数)。
然后对该函数进行研究,发现它有个很大的局部类对象,sizeof的长度达到600M。将该对象改造为堆对象后,问题解决了。
以下是探究这个问题后得到的结论:
clang++ -O2编译的程序中,一个未被实际调用到的函数中的局部变量如果可能被调用到,那进程会在main执行前就将可能的最大栈空间预先分配。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func_1()
{
char tmp[1024*1024*500];
memset(tmp, 0, sizeof(tmp));
printf("func_1() tmp=%s\n", tmp);
}
void func_2()
{
printf("func_2()\n");
}
int main(int argc, char *argv[])
{
int opt = atoi(argv[1]);
if (opt == 1)
{
func_1();
}
else if (opt == 2)
{
func_2();
}
return 0;
}
编译命令:
clang++ -g -O2 test.cpp -o test
使用gdb进行调试,查看在刚刚进入main函数时,栈空间是多大:
gdb --args ./test 2
(gdb) b main
(gdb) r
(gdb) p argv
$1 = (char **) 0x7fffffffe2b8 #此地址是保存命令行参数字符串值的起始位置,可以近似认为是程序实际使用到的栈空间起始位置,不过紧临它之前还会有很多环境变量字符串的信息,可能也会有几KB长度,先不考虑它
(gdb) n
(gdb) info proc mappings #此命令可以看到stack段的实际申请大小,从下面可以看到stack的大小0x1f402000(单位为KB)已经达到了512MB
0x7fff8f34a000 0x7fff8f34c000 0x2000 0x0 [vdso]
0x7fff8f34c000 0x7fff8f34d000 0x1000 0x21000 /usr/lib64/ld-2.17.so
0x7fff8f34d000 0x7fff8f34e000 0x1000 0x22000 /usr/lib64/ld-2.17.so
0x7fff8f34e000 0x7fff8f34f000 0x1000 0x0
0x7fffe0bfd000 0x7ffffffff000 0x1f402000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
从实际测试情况来看,优化策略会将函数栈中所有可能用到的栈空间进行计算得到一个最大值,然后在main执行之前就预先申请这么大的栈空间。如果是递归函数的话就直接忽略不去计算。
还有种情况是多线程的时候,只有主线程会计算这个最大栈空间大小去扩展stack区域的长度,对于子线程而言它们会按照ulimit -s的大小去申请栈空间。