c – 尝试移动大型bitset时GCC4.6上的Seg故障,这是编译器错误吗?

由于在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, 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

(标记的行仅存在于返回值的情况下,行 – 仅存在于移动情况中).

在移动案例中还有更多的堆栈指针操作(以及一些非常大的数字).至关重要的是,然后以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,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

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

解决方案:在堆上分配,或使用pthread_attr_setstacksize或在Linux上克隆获得更大的堆栈.

转载注明原文:c – 尝试移动大型bitset时GCC4.6上的Seg故障,这是编译器错误吗? - 代码日志