#事の起こり
xcodeでc++のコード実行時、main関数を抜けた際に、
Thread 1: signal SIGABRT
が起きてしまいました・・・。(簡単なコードなのにショック)
問題のコードはこちら。Runchクラスを2つ用意してコピーして内容表示するだけの簡単なもの。
class Runch{
public:
string* menu; //ポインタであることに注意
int value;
Runch() : menu(new string){}//コンストラクタ
~Runch(){//デストラクタ
delete menu; //フリーストアに確保されたmenuを解放
}
};
int main(void) {
Runch Sukiya;
*Sukiya.menu = "牛丼";
Sukiya.value = 400;
//Sukiyaの牛丼は400です。と表示
cout << "Sukiyaの"<<*Sukiya.menu << "は" << Sukiya.value << "です。"<<endl;
Runch Matuya(Sukiya);//MatuyaにSukiyaをコピー(コピーコンストラクタによるコピー)
//Matuyaの牛丼は400です。と表示
cout << "Matuyaの"<<*Matuya.menu << "は" << Matuya.value << "です。"<<endl;
return 0;
}
#原因
端的に言うと、Runchのメンバのmenuがポインタである事が原因でした。今回のコードで何が起きているのかと言うと・・・
- Runch Matuya(Sukiya)とした時に、コピーコンストラクタによってMatuyaのSukiyaの内容がコピーされる。
- コピーにより、SukiyaとMatuyaのメンバのmenuは同じアドレスを指すようになる。
- main関数終了時にRunchで明示的に定義したデストラクタが働き、フリーストアに確保されたMatuyaとSukiyaのmenuを解放する。
と言うことが起きています。一見なんの不備もないように思われますが、実は3で問題が起きていました。
デストラクタは基本的に、そのクラスが不要になった時(今回だったらmain関数が終わった時)に働くものです。今回は、SukiyaとMatuyaの二つのクラスがあるのでmain関数の終わりにはSukiyaとMatuyaのそれぞれの2つのコンストラクタが働いて、フリーストアに確保されているmenuを解放しようとします。
しかし、ここで2つのクラスのmenuは同じアドレスを指しています。すなわち、同じ物を2回解放しようとしてします。これにより、
Thread 1: signal SIGABRT
によるエラー終了が発生していたようです。
#対策
コピーコンストラクタを次のように明示的に定義して、コピーの際に、menuのアドレスを直でコピーしないようにしました。
class Runch{
public:
string* menu; //ポインタであることに注意
int value;
Runch() : menu(new string){}//コンストラクタ
~Runch(){//デストラクタ
delete menu; //フリーストアに確保されたmenuを解放
}
Runch(const Runch& x){//コピーコンストラクタを明示
string oldMenu = *x.menu; //コピー元のmenuを一時避難
menu= new string(oldMenu);//新たなmenuをフリーストアに確保
value = x.value;
}
};
int main(void) {
Runch Sukiya;
*Sukiya.menu = "牛丼";
Sukiya.value = 400;
//Sukiyaの牛丼は400です。と表示
cout << "Sukiyaの"<<*Sukiya.menu << "は" << Sukiya.value << "です。"<<endl;
Runch Matuya(Sukiya);//MatuyaにSukiyaをコピー(コピーコンストラクタによるコピー)
//Matuyaの牛丼は400です。と表示
cout << "Matuyaの"<<*Matuya.menu << "は" << Matuya.value << "です。"<<endl;
return 0;
}
このようにすると、Runch Matuya(Sukiya)とした時に、明示的に定義したコピーコンストラクタが働き、Sukiyaのmenu(Gyudon)がoldMenuとしてコピーされ、新たにフリーストアに確保したMatuyaのmenuにコピーされます。
こうすると、SukiyaとMatuyaのメンバ menuはそれぞれ別のアドレスを指すのでデストラクタによる2重解放を防ぐことができ、Thread 1: signal SIGABRTを消すことができました。
#まとめ
今回調べてみて分かったことですが、Thread 1: signal SIGABRTは今回のようなフリーストアに確保した資源の2重解放以外の原因でも発生するようです。(一つのエラーコードに複数の原因があるのはいかがなものか・・・)
今回のように2重解放だけが原因でないかもしれませんが、Thread 1: signal SIGABRTに出くわした時は、2重解放していないかどうか疑ってみてはどうでしょうか.