c++ - 尝试 move 大型位集时 GCC4.6 出现段错误,这是编译器错误吗?

标签 c++ gcc segmentation-fault undefined-behavior move-semantics

当位集很大时,由于返回位集的右值,我遇到了崩溃问题。这是编译器错误还是我错误地做了一些导致未定义行为的事情?

下面的代码在设置了 -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    $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), %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    $2097152, %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

(标有+的行只存在于按值返回的情况下,标有-的行只存在于 move 的情况下)

在 move 情况下(还有一些非常大的数字)正在进行更多的堆栈指针操作。至关重要的是,最后以 memcpy 调用结束,该调用将结果复制回堆栈。

我的分析是,对于按值返回的情况,实际上发生了另一种优化,这意味着对于按值返回的情况,main 中未使用的临时文件被完全省略,但不是 move 的情况。

我们可以通过使用 -O0 禁用所有优化并查看会发生什么来对按值(value)返回的情况执行相同的分析来进一步确认:

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    $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), %eax    #, tmp64
-   movl    %eax, (%esp)    # tmp64,
-   call    _ZSt4moveIRSt6bitsetILj16777215EEEONSt16remove_referenceIT_E4typeEOS4_  #
-   movl    %eax, %edx  #, D.21547
-   movl    8(%ebp), %eax   # .result_ptr, tmp65
-   movl    $2097152, %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

同样的堆栈指针操作和复制发生在按值返回的情况下禁用优化。所以看起来你在这两种情况下都有堆栈溢出,但在按值返回的情况下,由于其他优化,你的测试用例不足以实际观察到它。

解决方案:在堆上分配,或者在 Linux 上使用 pthread_attr_setstacksizeclone 获得更大的堆栈。

关于c++ - 尝试 move 大型位集时 GCC4.6 出现段错误,这是编译器错误吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30372986/

相关文章:

c - 错误 : invalid conversion from ‘void*’ to ‘arguments*’ [-fpermissive]

c++ - 我的 C++ 程序中的段错误

c++ - Qt:改变用户变量表示控件状态的改变

c - C代码中的SSE2指令

C++ 预处理器不知道模板参数?

c++ - gcc 和 msvc(或 Linux 和 Windows)中的 valarray 有什么区别

c++ - 为什么会发生这个段错误?

Python C 扩展嵌套字典段错误

c++ - 在 C++11 中, `i +=++i + 1` 是否表现出未定义的行为?

c++ - QT:从 QLineEdit 到不同类的 QLabel 的简单文本