2016-11-26 58 views
2

我是新来的C++。我不知道为什么下面的代码有分段错误。 Doo()是一个包含地图<>的类。你可以调用Doo :: start()来启动一个while循环线程。然后再调用Doo :: turnoff()来终止线程。我不知道我的代码有什么问题。请帮助我理解。分段错误,可能的原因:函数指针,多线程,stl映射等

#include <iostream> 
#include <thread> 
#include <map> 
#include <chrono> 


using namespace std; 

class Doo{ 
    int id; 
    bool _turnoff=false; 
    map<int,string> msg; 
public: 
    Doo(int _id); 
    void start(bool (*fptr)(map<int,string>&)); 
    void turnoff(); 
}; 

Doo::Doo(int _id){ 
    id = _id; 
    msg[1]="hello"; 
    msg[2]="nihao"; 
    msg[4]="conichiwa"; 
} 

void Doo::start(bool (*fptr)(map<int,string>&)){ 
    thread m_thr([&](){ 
     while(!_turnoff){ 
      this_thread::sleep_for(chrono::seconds(1)); 
      fptr(msg); 
     } 
    }); 
    m_thr.detach(); 
} 

void Doo::turnoff(){ 
    _turnoff=true; 
} 

bool hdl(map<int,string>& greet){ 
    cout<<greet[2]<<endl; 
    return true; 
} 

int main(void){ 
    Doo d(1); 
    d.start(hdl); 
    while(1){ 
     char x; 
     cin>>x; 
     if(x=='q'){ 
      cout<<"quit"<<endl; 
      d.turnoff(); 
      this_thread::sleep_for(chrono::seconds(1)); 
      break; 
     } 
    } 
    return 0; 
} 

我用下面的命令编译:

g++ p3.cpp -std=c++11 -pthread 

它编译没有任何问题。

Valgrind的结果:

==18849== Memcheck, a memory error detector 
==18849== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 
==18849== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 
==18849== Command: ./a.out 
==18849== 
==18849== 
==18849== Process terminating with default action of signal 11 (SIGSEGV) 
==18849== Bad permissions for mapped region at address 0x68C1700 
==18849== at 0x68C1700: ??? 
==18849== by 0x402799: void std::_Bind_simple<Doo::start(bool (*)(std::map<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&))::{lambda()#1}()>::_M_invoke<>(std::_Index_tuple<>) (in /home/xli1989/Projects/playground/a.out) 
==18849== by 0x4026EF: std::_Bind_simple<Doo::start(bool (*)(std::map<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&))::{lambda()#1}()>::operator()() (in /home/xli1989/Projects/playground/a.out) 
==18849== by 0x40267F: std::thread::_Impl<std::_Bind_simple<Doo::start(bool (*)(std::map<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&))::{lambda()#1}()> >::_M_run() (in /home/xli1989/Projects/playground/a.out) 
==18849== by 0x4EF2C7F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==18849== by 0x53D96F9: start_thread (pthread_create.c:333) 
==18849== by 0x56F5B5C: clone (clone.S:109) 
+1

看不到你违反任何规则,但在你做之前知道它们存在是个好主意。 [有关在C++标识符中使用下划线的规则是什么?](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-ac-identifier) – user4581301

+1

看来你认为在一秒钟睡眠之后,一秒睡眠会结束,但这并不能保证。 – Dani

回答

-1

问题在于拉姆达在thread m_thr。您通过引用捕获fptr,但是一旦函数执行,此引用就会被销毁。但是线程会尝试使用引用来销毁fptr并因分段错误而死亡。

解决方案很简单 - 按值捕获fptr,而不是通过引用。

thread m_thr([=](){ // changed & to = 
    while(!_turnoff){ 
     this_thread::sleep_for(chrono::seconds(1)); 
     fptr(msg); 
    } 
}); 
+0

当函数完成执行时,引用引用的对象是_not_销毁。它是“Doo”的成员,并且只要Doo的实例这样做就会保持活力。 – Snps

+0

通过价值捕获确实可以避免悬而未决的参考,但您对为什么是错误的理解。 – Snps

0

首先在_turnoff上有一个数据竞赛,所以你的例子将展示UB。请记住,什么都没有在C++中默认是原子的,所以你最好使用std::atomic或同步。现在


,你指的是这个问题源于服用参考到由主线程分配的自动对象(Doo::msg)线程的任务。由于工作线程从主线程中的句柄中分离出,这意味着主线程将能够在工作线程之前终止并销毁堆栈上的所有分配对象(主要销毁其自己的堆栈)。这导致工作线程保持对被销毁的对象的引用的可能性。

在main中发送关闭信号后,您只能等待/休眠与工作线程循环中等待/休眠相同的时间量。因此,工作者线程很有可能使用悬挂参考“最后完成”。

这通常不是问题,因为程序无论如何都会终止,但Valgrind仍然会抱怨。