読者です 読者をやめる 読者になる 読者になる

アーキテクチャをスマートに。

株式会社ネオジニア代表。ITアーキテクトとしてのお仕事や考えていることなどをたまに綴っています。(記事の内容は個人の見解に基づくものであり、所属組織を代表するものではありません)

Android NDK の C++ 開発でハマる(1)

C/C++ Java プログラミング

久しぶりにC言語でプログラミングしていて、ちょっとハマったことがありました。

53種類のカードの組み合わせを表現するのに、どんなデータ構造がよいでしょうか。
要するにトランプの手札のことです。
各カードに 1 〜 53 のIDを振って、そのIDの集合を管理すればよいですね。

Javaプログラマなら

// 単純にIDの配列として
int[] cards = {};

// または、追加削除のしやすさとパフォーマンスを考えて
List<Integer> cardlist = new LinkedList<Integer>();

// さらに数学的な『集合』に美しさを感じる理学派プログラマなら
Set<Integer> cardset = new HashSet<Integer>();

とかでしょうか。

脳みその切り替え

実はプラットフォームはAndroidです。
メモリやCPUリソースには限りがあります。
なので、Android NDK を使って C/C++ でやることにしました。

STLが使えるので、vector や set で同じようなデータ構造が考えられます。


いやいや、ちょっと待って下さい


それではわざわざ C でやる意味がありません。


Java脳からC脳へ(富豪脳から貧民脳へ)切り替える必要があります。(または、業務系から制御系へ切り替える、といってもいいかもしれませんね)

というわけでビットセットを使って表現する方法を考えました。

それぞれのカードを、持ってるか(1)、持ってないか(0)の1ビットで表せば、すべての組み合わせをたかだか53ビットあれば表現できます。
なので64bit変数ひとつあれば事足ります。(ビットフィールドを使えばあと9ビット他のデータに使えますね)

Javaでもビットセット使えばlong一個で表現出来るよ」というツッコミ、そのとおりです。
でも、Javaではマクロがないので、ビット操作を毎回コーディングしなければならず、またデータ構造を変換したい場合(たとえば順序付けのためにList化する)にそれ用のメソッドをいくつか用意することになり、それらをオブジェクト指向の世界にマッチするようにクラス化したりすると、結局インスタンスサイズは64bitを超えて
しまうことになりますね。
とまぁ実装技術の議論はさておき、それ以前に重要なのは、『考え方を切り替える』ということなのです。

PC上でデバッグしてAndroidへ移植

いきなりAndroid上でCのプログラムをデバッグするのは開発環境面からいって大変なので、
まずはPCでテストプログラムを書いて、デバッグしてからAndroidへ組み込む、という手順をとりました。

コーディング → PC上でテスト実行&デバッグ → JNIインタフェースを書いてAndroidへ組み込む

テストプログラムを書くということはとてもいい事だとつくづく思います。だってインタフェースを明確にして環境依存なコードを分離しないと、テストできないから、必然的にそういうプログラムに仕上がるし、考え方が身につきます。SI開発の現場でも積極的に導入すべきです。

ちなみにPC側の開発環境は Ubuntu Linux 11.10 + gcc4.6 です。

Androidで動かない(本題)

...
さて、前置きが長くなりました。

無事にPC上で動くようになったので、Androidへ組み込みました。
そんで動かしてみると、どうも動きが怪しい。
たまにセグメンテーションフォルトが発生して落ちてしまいます。

ログを仕込んで追いかけてみると、ビットセットが空になっていることがあるようです。(これを追いかけるのにま〜〜時間かかった)

おかしい箇所は以下のようなコードです。

void func(int cardId) {
 uint64_t mask = 1l << cardId;
 ...

変数 mask がゼロになってしまうようです。
PC上では、何回実行してもそんなこと起こりません。

さてクイズです。

なぜこんなことが起こるのでしょう?

※ここですぐにピンと来た人はすごいです。かなり詳しいですね。
※全然わからない人は普通です。

ヒント

ヒントです。
以下のように書き換えると上手くいきます。

void func(int cardId) {
// uint64_t mask = 1l << cardId;  // これではダメ
 uint64_t mask = 1l;
 mask <<= cardId;
 ...

※ここでピンと来た方は現役Cプログラマですね。
※皆目検討も付かない人はCプログラマとしてはちょっと(TxT)かも。。。

むずかしいですね。

答えは次のポストへ。