尝试移动大型位集时 GCC4.6 出现段错误,这是编译器错误吗?
Seg fault on GCC4.6 when trying to move a large bitset, is this a compiler bug?
我遇到过由于 return 位集较大时的右值而导致崩溃的问题。这是编译器错误还是我错误地做了一些导致未定义行为的事情?
下面的代码在设置了 -std=c++0x
标志的 GCC 4.6.3 上崩溃。
#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, and GNU GPL'd, 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 frees, 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 97176, %esp #,
- leal -2097160(%ebp), %eax #, tmp60
+ subl , %esp #,
+ movl 8(%ebp), %eax # .result_ptr, tmp59
movl 97152, %edx #, tmp61
movl %edx, 8(%esp) # tmp61,
movl [=11=], 4(%esp) #,
movl %eax, (%esp) # tmp60,
call memset #
- leal -2097160(%ebp), %eax #, tmp64
- movl %eax, (%esp) # tmp64,
- call std::remove_reference<std::bitset<16777215u>&>::type&& std::move<std::bitset<16777215u>&>(std::bitset<16777215u>&) #
- movl %eax, %edx #, D.21547
- movl 8(%ebp), %eax # .result_ptr, tmp65
- movl 97152, %ecx #, tmp68
- movl %ecx, 8(%esp) # tmp68,
- movl %edx, 4(%esp) # tmp67,
- movl %eax, (%esp) # tmp66,
- call memcpy #
movl 8(%ebp), %eax # .result_ptr,
leave
.cfi_restore 5
(标有+的行只存在于return by value的情况下,标有-的行只存在于move的情况下)。
移动案例中进行了更多的堆栈指针操作(还有一些非常大的数字)。至关重要的是,最后以 memcpy 调用结束,该调用将结果复制回堆栈。
我的分析是,对于 return by value 情况,实际上还有另一个优化正在发生,这意味着对于 return by value 情况,main 中未使用的临时文件被完全省略,但不是移动案例。
我们可以通过对 return 按值案例执行相同的分析并使用 -O0 禁用所有优化并查看会发生什么来进一步确认:
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,23 +79,13 @@
.cfi_offset 5, -8
movl %esp, %ebp #,
.cfi_def_cfa_register 5
- subl 97176, %esp #,
- leal -2097160(%ebp), %eax #, tmp60
+ subl , %esp #,
+ movl 8(%ebp), %eax # .result_ptr, tmp59
movl 97152, %edx #, tmp61
movl %edx, 8(%esp) # tmp61,
movl [=12=], 4(%esp) #,
movl %eax, (%esp) # tmp60,
call memset #
- leal -2097160(%ebp), %eax #, tmp64
- movl %eax, (%esp) # tmp64,
- call _ZSt4moveIRSt6bitsetILj16777215EEEONSt16remove_referenceIT_E4typeEOS4_ #
- movl %eax, %edx #, D.21547
- movl 8(%ebp), %eax # .result_ptr, tmp65
- movl 97152, %ecx #, tmp68
- movl %ecx, 8(%esp) # tmp68,
- movl %edx, 4(%esp) # tmp67,
- movl %eax, (%esp) # tmp66,
- call memcpy #
movl 8(%ebp), %eax # .result_ptr,
leave
.cfi_restore 5
再次发生相同的堆栈指针操作和复制,但在 return by value 情况下禁用了优化。所以看起来你在这两种情况下都有堆栈溢出,但在 return by value 情况下,由于其他优化,你的测试用例不足以实际观察到它。
解决方案:在堆上分配,或者在 Linux.
上使用 pthread_attr_setstacksize
或 clone
获得更大的堆栈
我遇到过由于 return 位集较大时的右值而导致崩溃的问题。这是编译器错误还是我错误地做了一些导致未定义行为的事情?
下面的代码在设置了 -std=c++0x
标志的 GCC 4.6.3 上崩溃。
#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, and GNU GPL'd, 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 frees, 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 97176, %esp #,
- leal -2097160(%ebp), %eax #, tmp60
+ subl , %esp #,
+ movl 8(%ebp), %eax # .result_ptr, tmp59
movl 97152, %edx #, tmp61
movl %edx, 8(%esp) # tmp61,
movl [=11=], 4(%esp) #,
movl %eax, (%esp) # tmp60,
call memset #
- leal -2097160(%ebp), %eax #, tmp64
- movl %eax, (%esp) # tmp64,
- call std::remove_reference<std::bitset<16777215u>&>::type&& std::move<std::bitset<16777215u>&>(std::bitset<16777215u>&) #
- movl %eax, %edx #, D.21547
- movl 8(%ebp), %eax # .result_ptr, tmp65
- movl 97152, %ecx #, tmp68
- movl %ecx, 8(%esp) # tmp68,
- movl %edx, 4(%esp) # tmp67,
- movl %eax, (%esp) # tmp66,
- call memcpy #
movl 8(%ebp), %eax # .result_ptr,
leave
.cfi_restore 5
(标有+的行只存在于return by value的情况下,标有-的行只存在于move的情况下)。
移动案例中进行了更多的堆栈指针操作(还有一些非常大的数字)。至关重要的是,最后以 memcpy 调用结束,该调用将结果复制回堆栈。
我的分析是,对于 return by value 情况,实际上还有另一个优化正在发生,这意味着对于 return by value 情况,main 中未使用的临时文件被完全省略,但不是移动案例。
我们可以通过对 return 按值案例执行相同的分析并使用 -O0 禁用所有优化并查看会发生什么来进一步确认:
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,23 +79,13 @@
.cfi_offset 5, -8
movl %esp, %ebp #,
.cfi_def_cfa_register 5
- subl 97176, %esp #,
- leal -2097160(%ebp), %eax #, tmp60
+ subl , %esp #,
+ movl 8(%ebp), %eax # .result_ptr, tmp59
movl 97152, %edx #, tmp61
movl %edx, 8(%esp) # tmp61,
movl [=12=], 4(%esp) #,
movl %eax, (%esp) # tmp60,
call memset #
- leal -2097160(%ebp), %eax #, tmp64
- movl %eax, (%esp) # tmp64,
- call _ZSt4moveIRSt6bitsetILj16777215EEEONSt16remove_referenceIT_E4typeEOS4_ #
- movl %eax, %edx #, D.21547
- movl 8(%ebp), %eax # .result_ptr, tmp65
- movl 97152, %ecx #, tmp68
- movl %ecx, 8(%esp) # tmp68,
- movl %edx, 4(%esp) # tmp67,
- movl %eax, (%esp) # tmp66,
- call memcpy #
movl 8(%ebp), %eax # .result_ptr,
leave
.cfi_restore 5
再次发生相同的堆栈指针操作和复制,但在 return by value 情况下禁用了优化。所以看起来你在这两种情况下都有堆栈溢出,但在 return by value 情况下,由于其他优化,你的测试用例不足以实际观察到它。
解决方案:在堆上分配,或者在 Linux.
上使用pthread_attr_setstacksize
或 clone
获得更大的堆栈