質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.86%

  • C++

    3011questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • COCOS2D-X

    168questions

    COCOS2D-Xは、 2Dゲームを手軽に開発できるフレームワークのことです。 iPhone(iOS)向け、Android等に対応しており、 実質ワンソースで開発が可能です。

Cocos2d-x addChild()でセグメンテーション違反になる

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 517

yama_da

score 65

こんばんは

前提・実現したいこと

今「cocos2d-xではじめるスマートフォンゲーム開発」という本を見ながらcocos2d-xを勉強中なのですが、ここに書いてあるサンプルコードをそのまま入力したのにも関わらずセグメンテーション違反でコアダンプしてしまいます。gdbでcoreファイルを見てみたのですが、イマイチ原因が分かりません。どなたかご教授ください。。。

発生している問題・エラーメッセージ

以下、gdbでcoreファイルを見た時の出力です。

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x0000000000866e7b in cocos2d::Node::addChild (this=0x359fac0, child=0x36133b0) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCNode.cpp:989
#2  0x00000000008152fe in MainScene::<lambda()>::operator()(void) const (__closure=0x35d6710) at /home/daichi/dev/cocos.projects/KawazCatch/Classes/MainScene.cpp:407
#3  0x00000000008161f4 in std::_Function_handler<void(), MainScene::addReadyLabel()::<lambda()> >::_M_invoke(const std::_Any_data &) (__functor=...) at /usr/include/c++/4.9/functional:2039
#4  0x0000000000824240 in std::function<void ()>::operator()() const (this=0x360bd68) at /usr/include/c++/4.9/functional:2440
#5  0x0000000000822b9f in cocos2d::CallFunc::execute (this=0x360bd00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInstant.cpp:414
#6  0x0000000000822b04 in cocos2d::CallFunc::update (this=0x360bd00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInstant.cpp:403
#7  0x000000000082553c in cocos2d::Sequence::update (this=0x3614e40, t=1) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:411
#8  0x0000000000825388 in cocos2d::Sequence::update (this=0x3614ec0, t=1) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:385
#9  0x0000000000824923 in cocos2d::ActionInterval::step (this=0x3614ec0, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:142
#10 0x0000000000a0195c in cocos2d::ActionManager::update (this=0x2a68cd0, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionManager.cpp:449
#11 0x00000000009a5543 in void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}::operator()(float) const (__closure=0x2a66350, dt=0.0157120004)
    at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCScheduler.h:284
#12 0x00000000009a6e9a in std::_Function_handler<void (float), void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}>::_M_invoke(std::_Any_data const&, float) (__functor=..., __args#0=0.0157120004) at /usr/include/c++/4.9/functional:2039
#13 0x00000000008302e6 in std::function<void (float)>::operator()(float) const (this=0x2a68d30, __args#0=0.0157120004) at /usr/include/c++/4.9/functional:2440
#14 0x00000000009d190c in cocos2d::Scheduler::update (this=0x2a68790, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCScheduler.cpp:850
#15 0x00000000009a135b in cocos2d::Director::drawScene (this=0x2a65500) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCDirector.cpp:277
#16 0x00000000009a4ef8 in cocos2d::Director::mainLoop (this=0x2a65500) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCDirector.cpp:1443
#17 0x000000000090efc3 in cocos2d::Application::run (this=0x7ffd78757d00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/platform/linux/CCApplication-linux.cpp:86
#18 0x000000000081bbdb in main (argc=1, argv=0x7ffd78757e18) at /home/daichi/dev/cocos.projects/KawazCatch/proj.linux/main.cpp:14
(gdb) frame 1
#1  0x0000000000866e7b in cocos2d::Node::addChild (this=0x359fac0, child=0x36133b0) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCNode.cpp:989
989        this->addChild(child, child->getLocalZOrder(), child->_name);
(gdb) frame 2
#2  0x00000000008152fe in MainScene::<lambda()>::operator()(void) const (__closure=0x35d6710) at /home/daichi/dev/cocos.projects/KawazCatch/Classes/MainScene.cpp:407
407                    this->addChild(start);
(gdb) p start
$3 = (cocos2d::Sprite * const) 0x36133b0
(gdb) p this
$4 = (MainScene * const) 0x359fac0
(gdb) 

該当のソースコード

以下、サンプルコード内の該当する関数です。サンプルコードは、落ちてくるフルーツをキャッチしてスコアを競うというゲームです。

void MainScene::addReadyLabel()
{
    auto winSize = Director::getInstance()->getWinSize();
    auto center = Vec2(winSize.width / 2.0,winSize.height / 2.0);
    //Readyの文字を定義する
    auto ready = Sprite::create("ready.png");
    ready->setScale(0);  //最初の大きさを0%にしておく
    ready->setPosition(center);
    this->addChild(ready);

    //STARTの文字を定義する
    auto start = Sprite::create("start.png");
    start->runAction(Sequence::create(
             CCSpawn::create(
                 EaseIn::create(ScaleTo::create(0.5,0.5),0.5),
                 FadeOut::create(0.5),
                 NULL),
             RemoveSelf::create(),
             NULL));

    start->setPosition(center);

    //READYにアニメーションを追加する
    ready->runAction(Sequence::create(
            ScaleTo::create(0.25,1),
            DelayTime::create(1.0),
            CallFunc::create([this,start] {
                //STARTのラベルを追加する
                this->addChild(start);
                //ゲームの状態をPLAYINGに切り替える
                _state = GameState::PLAYING;

                CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("start.mp3");
            }),
            RemoveSelf::create(),
            NULL));
}


addReadyLabel()関数は、ゲームの開始時に「READY」と「START」の画像を順にアニメーションを行いながら表示する関数です。まず「READY」を表すSpriteをアニメーションを行いながら表示して、アニメーションが終わるとCallFuncのラムダのなかで「START」を表すSpriteをSceneに追加しています。
CallFuncのラムダ内のthis->addChild(start);の行をコメントアウトするとエラーは発生しないので、この行でエラーが発生しているのは分かるのですが、原因がわかりません。

試したこと

初めラムダ内のthisかstartがヌルになっているんじゃないかと思ったのですが、gdbのpコマンドで確認してみるとどちらもヌルではありませんでした。もしかしたらstartにセットしたアニメーション内に原因が?と思い、一応start->runAction()の部分をコメントアウトして試して見たのですが、やはりaddChild(start)の部分でエラーになり、お手上げ状態です。どこが原因でしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

私も同じ本を持っていてこれで勉強しました。
実はこのコードには根本的な誤りがあります。

 C++のポインタの特性について

C++のポインタは参照先のオブジェクトのアドレスを保持しているだけで実体ではないため、
例えば、startの参照先であるSpriteが破棄されたとしても、startの値は変化しないのでNULLになりません。
なので、ポインタがNULLでないからといってオブジェクトが存在するという事にはなりません。

 cocos2d-xのリファレンスカウンタについて

cocos2d-xのオブジェクト(Nodeを継承したクラス)には、リファレンスカウンタがあり、
メインループごとにオブジェクトを自動で破棄する仕組みがあります。

本書のp70-74にも記載がありますが、こちらの記事が非常に分かりやすいのでご参照ください。
cocos2d-xのリファレンスカウンタを理解してクラッシュやメモリリークを防ぐ | たそがれブランチ

ざっくりまとめると、

  • SpriteなどのNodeを継承したオブジェクトを作成すると、リファレンスカウンタは1の状態で作成される
  • addChildするか、自分でretain()を呼び出すと、リファレンスカウンタが+1される
  • リファレンスカウンタが1のオブジェクトは、メインループで自動的に破棄される
  • この仕組みがあるため、cocos2d-xではポインタがNULLでなくてもオブジェクトが既に存在しないという事が起こる

 このコードの誤りについて

上記の特徴から、リファレンスカウンタが1のオブジェクトは自動的に破棄される。
明示的にretainやaddChildされていないオブジェクトは、こちらの意図しないところで破棄されてしまう。
という事が分かると思います。

もう一度、addReadyLabelのコードを見てみるとstartがaddChildされるのは、
ready->runAction()のCallFuncですが、ScaleToの0.25秒+DelayTimeの1秒で1.25秒後から開始されます。
cocos2d-xのメインループはデフォルトでは60FPSに設定されているので、は1/60秒=0.016秒ごとにメインループが実行されます。
つまり、0.016秒ごとにオブジェクトの破棄判定がされているという事です。
となると、startがaddChildされる前に、オブジェクトの自動破棄が行われる可能性があると考える事ができます。

 どのように修正すべきか?

オブジェクトの作成とaddChildの時間がずれている事が問題です。
このコードでは、オブジェクトを作ってすぐにaddChildすればいいだけなので、
ready->runActionのCallFunc内でstartの作成も行えば良いはずです。

void MainScene::addReadyLabel()
{
    auto winSize = Director::getInstance()->getWinSize();
    auto center = Vec2(winSize.width / 2.0,winSize.height / 2.0);
    //Readyの文字を定義する
    auto ready = Sprite::create("ready.png");
    ready->setScale(0);  //最初の大きさを0%にしておく
    ready->setPosition(center);
    this->addChild(ready);

    //READYにアニメーションを追加する
    ready->runAction(Sequence::create(
            ScaleTo::create(0.25,1),
            DelayTime::create(1.0),
            CallFunc::create([this, center] {

                //STARTの文字を定義する
                auto start = Sprite::create("start.png");
                start->runAction(Sequence::create(
                    CCSpawn::create(
                        EaseIn::create(ScaleTo::create(0.5,0.5),0.5),
                        FadeOut::create(0.5),
                        NULL),
                    RemoveSelf::create(),
                    NULL));

                start->setPosition(center);

                //STARTのラベルを追加する
                this->addChild(start);

                //ゲームの状態をPLAYINGに切り替える
                _state = GameState::PLAYING;

                CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("start.mp3");
            }),
            RemoveSelf::create(),
            NULL));
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/10 15:24

    詳しい回答ありがとうございます、助かりました!無事実行できるようになりました。
    問題は解決したので良いのですが、まだ疑問が残っていて、
    >つまり、0.016秒ごとにオブジェクトの破棄判定がされているという事です。
    >となると、startがaddChildされる前に、オブジェクトの自動破棄が行われる可能性があると考える事ができ>ます。
    とありますが、元のコードではCallFuncのラムダの中でstartをキャプチャしていると思うのですが、このキャプチャはオブジェクトの実体ではなくオブジェクトのポインタをコピーしているということですか?もしそうなら、キャプチャでポインタをコピーしたところでaddChild()を実行する頃にはもうすでに実体はautoreleaseによって破棄されているので、コピーしたポインタはヌルポインタになってしまっていた、ということでしょうか?

    キャンセル

  • 2017/01/10 15:31

    autoは便利なんですが、自分が何をやっているのか分からなくなるので微妙な感じはありますが、
    auto start = Sprite::create("start.png");
    これは、
    Sprite* start = Sprite::create("start.png");
    という事です。
    従って、startは単なるポインタであり、CallFuncのラムダでキャプチャで渡しているのはポインタ=アドレスです。

    > もしそうなら、キャプチャでポインタをコピーしたところでaddChild()を実行する頃にはもうすでに実体はautoreleaseによって破棄されているので、コピーしたポインタはヌルポインタになってしまっていた、ということでしょうか?

    はい、私の理解ではそうです。
    C++は参照先のオブジェクトが破棄されたらポインタを自動でNULLにしてくれないので、
    あれ?ポインタがNULLになってないのにエラーで落ちたぞ?という事が頻発します。

    キャンセル

  • 2017/01/10 16:26

    >autoは便利なんですが、自分が何をやっているのか分からなくなるので微妙な感じはありますが、
    本当にそうですね、自分もstartがポインタであることを完全に忘れていました。
    納得できました、ありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.86%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C++

    3011questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • COCOS2D-X

    168questions

    COCOS2D-Xは、 2Dゲームを手軽に開発できるフレームワークのことです。 iPhone(iOS)向け、Android等に対応しており、 実質ワンソースで開発が可能です。