由于在bitset很大时返回了bitset的rvalue,我遇到了崩溃问题.这是一个编译器错误还是我错误地做了导致未定义行为的事情?
下面的代码在GCC 4.6.3上崩溃,并设置了-std = c 0x标志.
#include <bitset> // typedef std::bitset<0xffff> uut; typedef std::bitset<0xffffff> uut; struct foo { foo(uut b) : b_(std::move(b)) { } uut b_; }; uut make_bits(int) { uut bits; // Only works for 0xffff: return std::move(bits); // Works for both 0xffff and 0xffffff: //return bits; } int main() { foo(make_bits(0)); }
奇怪的是,如果我删除int参数就可以了,也许这会导致函数被内联?
正如@unwind建议的那样,输出是在valgrind ./a.out下运行的:
==24780== Memcheck,a memory error detector ==24780== Copyright (C) 2002-2011,and GNU GPL'd,by Julian Seward et al. ==24780== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info ==24780== Command: ./a.out ==24780== ==24780== Warning: client switching stacks? SP change: 0x7ff000068 --> 0x7fea00058 ==24780== to suppress,use: --max-stackframe=6291472 or greater ==24780== Invalid write of size 8 ==24780== at 0x4005E5: main (in /home/sam/scratch/a.out) ==24780== Address 0x7fea00058 is on thread 1's stack ==24780== ==24780== Warning: client switching stacks? SP change: 0x7fea00050 --> 0x7fe800040 ==24780== to suppress,use: --max-stackframe=2097168 or greater ==24780== Invalid write of size 8 ==24780== at 0x40056F: make_bits(int) (in /home/sam/scratch/a.out) ==24780== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24780== Address 0x7fe800048 is on thread 1's stack ==24780== ==24780== ==24780== Process terminating with default action of signal 11 (SIGSEGV) ==24780== Access not within mapped region at address 0x7FE800048 ==24780== at 0x40056F: make_bits(int) (in /home/sam/scratch/a.out) ==24780== If you believe this happened as a result of a stack ==24780== overflow in your program's main thread (unlikely but ==24780== possible),you can try to increase the size of the ==24780== main thread stack using the --main-stacksize= flag. ==24780== The main thread stack size used in this run was 8388608. ==24780== ==24780== Process terminating with default action of signal 11 (SIGSEGV) ==24780== Access not within mapped region at address 0x7FE800039 ==24780== at 0x4A255A0: _vgnU_freeres (in /usr/lib/valgrind/vgpreload_core-amd64-linux.so) ==24780== If you believe this happened as a result of a stack ==24780== overflow in your program's main thread (unlikely but ==24780== possible),you can try to increase the size of the ==24780== main thread stack using the --main-stacksize= flag. ==24780== The main thread stack size used in this run was 8388608. ==24780== ==24780== HEAP SUMMARY: ==24780== in use at exit: 0 bytes in 0 blocks ==24780== total heap usage: 0 allocs,0 frees,0 bytes allocated ==24780== ==24780== All heap blocks were freed -- no leaks are possible ==24780== ==24780== For counts of detected and suppressed errors,rerun with: -v ==24780== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 2 from 2)
并且使用valgrind –max-stacksize = 99999999 ./a.out,因为valgrind促使我:
==24790== Memcheck,a memory error detector ==24790== Copyright (C) 2002-2011,by Julian Seward et al. ==24790== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info ==24790== Command: ./a.out ==24790== ==24790== Warning: client switching stacks? SP change: 0x7ff000068 --> 0x7fea00058 ==24790== to suppress,use: --max-stackframe=6291472 or greater ==24790== Invalid write of size 8 ==24790== at 0x4005E5: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fea00058 is on thread 1's stack ==24790== ==24790== Warning: client switching stacks? SP change: 0x7fea00050 --> 0x7fe800040 ==24790== to suppress,use: --max-stackframe=2097168 or greater ==24790== Invalid write of size 8 ==24790== at 0x40056F: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800048 is on thread 1's stack ==24790== ==24790== Invalid write of size 4 ==24790== at 0x400576: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800044 is on thread 1's stack ==24790== ==24790== Invalid write of size 8 ==24790== at 0x400590: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800038 is on thread 1's stack ==24790== ==24790== Invalid write of size 4 ==24790== at 0x4C2E0E0: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x400594: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800050 is on thread 1's stack ==24790== ==24790== Invalid write of size 4 ==24790== at 0x4C2E0EB: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x400594: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800058 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2E10E: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800038 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4005A7: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800048 is on thread 1's stack ==24790== ==24790== Invalid write of size 8 ==24790== at 0x4C2D10D: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x4005C0: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fee00058 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D11A: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x4005C0: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe9fffc8 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D108: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x4005C0: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe9fffc0 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4005C1: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4005E9: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fe800048 is on thread 1's stack ==24790== ==24790== Warning: client switching stacks? SP change: 0x7fe800040 --> 0x7fea00050 ==24790== to suppress,use: --max-stackframe=2097168 or greater ==24790== further instances of this message will not be shown. ==24790== Invalid read of size 8 ==24790== at 0x4005C9: make_bits(int) (in /home/sam/scratch/a.out) ==24790== by 0x4E5376C: (below main) (libc-start.c:226) ==24790== Address 0x7fea00058 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D000: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x40060A: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fec00060 is on thread 1's stack ==24790== ==24790== Invalid write of size 8 ==24790== at 0x4C2D004: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x40060A: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fea00060 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D00F: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x40060A: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fec00070 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D108: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x400650: foo::foo(std::bitset<16777215ul>) (in /home/sam/scratch/a.out) ==24790== by 0x400612: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fec00058 is on thread 1's stack ==24790== ==24790== Invalid read of size 8 ==24790== at 0x4C2D11A: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x400650: foo::foo(std::bitset<16777215ul>) (in /home/sam/scratch/a.out) ==24790== by 0x400612: main (in /home/sam/scratch/a.out) ==24790== Address 0x7fec00048 is on thread 1's stack ==24790== ==24790== Invalid write of size 8 ==24790== at 0x4C2D10D: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==24790== by 0x400650: foo::foo(std::bitset<16777215ul>) (in /home/sam/scratch/a.out) ==24790== by 0x400612: main (in /home/sam/scratch/a.out) ==24790== Address 0x7feffffe0 is on thread 1's stack ==24790== ==24790== ==24790== HEAP SUMMARY: ==24790== in use at exit: 0 bytes in 0 blocks ==24790== total heap usage: 0 allocs,0 bytes allocated ==24790== ==24790== All heap blocks were freed -- no leaks are possible ==24790== ==24790== For counts of detected and suppressed errors,rerun with: -v ==24790== ERROR SUMMARY: 2097097 errors from 19 contexts (suppressed: 2 from 2)
解决方法
通过使用-S编译两个案例,我们可以确切地看到GCC正在做什么:
g++-4.6 -std=c++0x test.cc -S -fverbose-asm
然后使用diff来比较输出:
diff -rNu move.s ret.s |c++filt --- move.s 2015-05-21 14:00:49.097524035 +0100 +++ ret.s 2015-05-21 14:00:40.021510019 +0100 @@ -79,23 +79,13 @@ .cfi_offset 5,-8 movl %esp,%ebp #,.cfi_def_cfa_register 5 - subl $2097176,%esp #,- leal -2097160(%ebp),%eax #,tmp60 + subl $24,%esp #,+ movl 8(%ebp),%eax # .result_ptr,tmp59 movl $2097152,%edx #,tmp61 movl %edx,8(%esp) # tmp61,movl $0,4(%esp) #,movl %eax,(%esp) # tmp60,call memset # - leal -2097160(%ebp),tmp64 - movl %eax,(%esp) # tmp64,- call std::remove_reference<std::bitset<16777215u>&>::type&& std::move<std::bitset<16777215u>&>(std::bitset<16777215u>&) # - movl %eax,D.21547 - movl 8(%ebp),tmp65 - movl $2097152,%ecx #,tmp68 - movl %ecx,8(%esp) # tmp68,- movl %edx,4(%esp) # tmp67,- movl %eax,(%esp) # tmp66,- call memcpy # movl 8(%ebp),leave .cfi_restore 5
(标记的行仅存在于返回值的情况下,行 – 仅存在于移动情况中).
在移动案例中还有更多的堆栈指针操作(以及一些非常大的数字).至关重要的是,然后以memcpy调用结束,将结果复制回堆栈.
我对它的分析是,对于按值返回的情况,实际上发生了另一个优化,这意味着未使用的临时内部主要完全被省略为返回值的情况,而不是移动情况.
我们可以通过对-O0执行相同的分析来进一步确认,并禁用所有的optimsisations并查看发生的情况:
diff -Nru noopt.s ret.s --- noopt.s 2015-05-21 14:06:14.798028762 +0100 +++ ret.s 2015-05-21 14:00:40.021510019 +0100 @@ -3,7 +3,7 @@ # compiled by GNU C version 4.6.4,GMP version 5.1.3,MPFR version 3.1.2-p3,MPC version 1.0.1 # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 # options passed: -imultilib . -imultiarch i386-linux-gnu -D_GNU_SOURCE -# test.cc -mtune=generic -march=i686 -O0 -std=c++0x -fverbose-asm +# test.cc -mtune=generic -march=i686 -std=c++0x -fverbose-asm # -fstack-protector # options enabled: -fasynchronous-unwind-tables -fauto-inc-dec # -fbranch-count-reg -fcommon -fdelete-null-pointer-checks -fdwarf2-cfi-asm @@ -79,- call _ZSt4moveIRSt6bitsetILj16777215EEEONSt16remove_referenceIT_E4typeEOS4_ # - movl %eax,leave .cfi_restore 5
同样,堆栈指针操作和复制发生,并且在返回值的情况下禁用了优化.所以看起来你在两种情况下都有堆栈溢出,但是在按值返回的情况下,由于其他的操作,你的测试用例不足以实际观察它.
解决方案:在堆上分配,或使用pthread_attr_setstacksize或在Linux上克隆获得更大的堆栈.