2015/10/10

[C]externは怖い

仕事ではC#とC/C++、趣味でJavaを使っています。
それぞれ用途に合わせて使っているので混乱することは意外とないのですが、それでもおかしなことをやらかすことはあります。

Cで何気なく書いた、絶対安全だと思っていたコードがクラッシュを引き起こしました

//////data.c////////

const char table[1]={1};

//////logic.c////////

extern const char* table;

if(table[0]==1){

//////////////


運用上の理由があり、どうしてもデータとロジックを分ける必要がありました。
かなりコードサイズに制約がある組込ファームウエアなので、constの定数についてはアクセス関数を使わずに直接externでリンクすることを許していました。
externをヘッダに書かなかったのは、絶対にこの場所でしか参照しないデータで、かつ第三者に存在を公開するのを避けたかったからです

私はtable[]とtable*は表現の違いだけで、同じものだと思っていました
だって、これが成り立つのは自明なので

///////////////

const char table[1]={1};

const char*p = table;

if(p[0]==1){

///////////////

さらに、これならクラッシュしないことも確認しました

//////data.c////////

const char table[4]={1,2,3,4};

//////logic.c////////

extern const char* table;

if(table[0]==1){

//////////////

添え字が1の時はコンパイラが勝手に最適化して定数として展開するのでしょうか?それはおかしい。だってデータ数を知っているのはdata.cの方で、logic.cはサイズを知らないのだから、配列サイズによって振る舞いを変えるということはできないはずです。

こうすればクラッシュしないこともわかりました

//////data.c////////

const char table[1]={1};

//////logic.c////////

extern const char table[];

if(table[0]==1){

//////////////

いよいよわからなくなってきました。
なにしろ私は[]と*はバイナリにしたら同じだと思っていましたから。
逆アセンブルしてみると、バイトコードには差異がありました。
なんだ、この差は?

…純粋なCプログラマの方々はおそらく私が何をいってるんだろうかと失笑していることでしょうね。はい、私も気が付いた時には笑いました。

まず第一に[]と*は違うものです。
説明には諸説あるようですが、*がポインタを表すのはおなじみだと思いますが、table[]は配列をあらわし、tableと書くと配列の先頭のアドレスを指すというルールがあるもののtableがポインタになっているわけではないのです。
それが証拠に下の式は成り立ちませんよね?

//////////////

const char table1[]={1};
const char table2[]={2};

table1 = table2;

//////////////

配列はポインタに代入できるが、ポインタではない。extern const*table;は代入ではないのでconst char table[]と等価ではない、実際には*を使うことによってポインタとご認識をして操作するので異常終了していた、というだけの話でした。

ここでの教訓はいくつかあります
ひとつめ、JavaやC#に慣れすぎていた私はconst char[]をextern char*で参照したものがコンパイルエラーにもリンクエラーにもならなかったので、うまく連結していると思い込んでしまったのですが、CのLinkerはシンボルを名前で連結しているだけで型なんて見てくれていない、ということをすっかり忘れていました。
C怖いよC

もうひとつはexternはヘッダに定義して参照元と参照先が同じ型であることを証明しなければならないという基本を無視してはいけないということです。
このルールはMisra-Cでも規定されています。
今回の例でも、定義をヘッダにしていれば、コンパイラが文句を言ってくれました

//////data.h////////

extern const char* table;

//////data.c////////

#include "data.h"

const char table[1]={1};

//////logic.c////////

#include "data.h"

if(table[0]==1){

//////////////

この場合、data.hの定義ではdata.cのコンパイルが成功しません
当たり前ですね、型がちがうんですから

そして最後の3つ目、Cのexternはでたらめで怖いということ

やっぱり自分は高級言語の方が好きです

2015/08/15

[Android][Wear]リリースパッケージはモバイルとセットで

Android Wear対応のアプリをGoogle Playにリリースしたら、Googleからこんなメールが来ました。

////////////////////////

Hello,

Thanks for submitting your app for Android Wear. This is a notification that your app, Fairy in a watch, with package name com.nag.android.fairyinawatch, has not been accepted as an Android Wear app on Google Play.

Version Name: 1.0.0
Version code: 1
Production track: PRODUCTION

REASON(S) FOR EXCLUSION:


-Your app does not have Android Wear functionality that’s visible to the user.

If you’re adding wearable features to notifications, make sure that they are triggering when appropriate.

If you’re building a wearable app, there may have been issues packaging it. Be sure to:
- Include all the permissions declared in the manifest file of the wearable app in the manifest file of the mobile app. For example, if you specify the VIBRATE permission for the wearable app, you must also add that permission to the mobile app.
- Ensure that both the wearable and mobile APKs have the same package name and version number.
- Sign your app.
- Test your app on a variety of different Android Wear devices and configurations.

////////////////////////

私のアプリはWear上だけで動くものなので、Wearのapkをアップしていましたが、それを指摘されたようです。
エミュレータでは普通に動いていたのでなにも疑わなかったのですが、Wearアプリはモバイルアプリのセットとしてリリースしなければならないのですね。
Wearデバイスが直接Google Playに接続できない都合なのだと思います。
確かにWearはモバイルのサブ端末としてデザインされた側面が大きいとは思いますが、私のように単体で動くアプリだって用途にはあるはず、どんなものなんですかね。

仕方がないので、なにもしないモバイル用のアプリを作って、WearアプリをDependencyに加えてリリースパッケージを作りました。
実際にはAndroid Studioでプロジェクトを新規作成するときにPhoneとWearの両方にチェックを入れて作れば勝手に紐づけてくれるので何の手間もありませんでした。

私のアプリは何のPermissionも要求しないものでしたが、Permissionが必要なアプリの場合はダミーのモバイルアプリにも同じPermissionをつけなければならないようです。

またGoogleさんに怒られるかもしれませんが、無料でチェックしてくれてると思えば便利なものです。

2015/07/12

[Android]USBケーブルの不良

新しいPCにAndroid Studioをインストールして、開発作業を開始しようと思ったら不思議なところでハマりました。

端末を接続しているのに実行できません。
SDKのバージョンがインストールされていなかったり、端末のドライバがインストールされていなかったり、一渡りやらかしていたのはいたのですが、それでも実行まで進みません。

Androidは比較的親切にできていて、設定がうまくないときには何をしたら対応できるのかを親切に説明しているエラーが出るものなのですが、実行した時にエラーは出ていません。
ただただ端末でアプリが実行されません。"Uploading file"まで進むのですが、そこから進んでいないようです。
端末を接続するとOSが反応しますし、実行要求中にケーブルを抜くとエラーが出ますので、端末を認識していないわけでもないようです。

アプリが悪いのかなとシンプルなもの、実績のあるものでやってみたり、Rebuildを繰り返してみたり、Edit Configurationを確認したりしましたがどうしようもなし。


半日はまって途方に暮れたところで、何気なくUSBを別なスロットに差し替えたら一度だけ実行に成功しました。
もう一度トライしたらダメになっていました。
デスクトップPCだったので、背面のUSBポートに挿したらそこそこ安定して実行できるようになりました。

USBケーブルの不良でデバッグができなくなったのはもう何度目かになります。
ただ、以前はデバッグができなくなったときには端末の接続そのものができなくなっていたので、今回は大丈夫だと思い込んでいたのが失敗でした。

USBの規格の細かいところは存じませんが、私が体験した限り下の4つの状態があるようです。
1.正常に接続できて、デバッグできる
2.情報デバイスとしてPCと接続できているが、ADBでデバッグできない(ダウンロードが終わらない、今回の状況)
3.情報デバイスとして接続できていないが、充電はできている
4.充電さえできない。故障

安売りのケーブルだとすぐに3の状態になってしまうような気がしていますが、実際どうなんでしょうか。端末本体のプラグを痛めてしまって修理に出すことになってしまったり、USBケーブルには祟られています。
今回使っていたケーブルは再三のトラブルに懲りて結構お高いものを使っていたのですが、残念です。
どんなケーブルを買えばいいのか、またお店で悩んでしまいそうです。

2015/05/18

[Java][Android]なぜガーベージコレクションなのか


なぜ、Javaはガーベージコレクション(GC)を採用したのかおさらいしたいと思います。
ポインタを隠ぺいしたかった、スマートポインタを言語的に実現したかった、というのは的を得ていません。
それらはが逐次処理型のメモリマネージャでも実現可能です。

理由は至ってシンプルです。それはGCが”早い”からです。
GCの”遅さ”に辟易している方々には意外な話かもしれませんが、少し説明を聞いてください。

逐次処理の場合、メモリの解放が行われるタイミングで必ずその処理を実行しなければなりません。
処理をするということは、時間がかかるということです。
一方ガーベージコレクションを持っているメモリマネージャは参照カウンタを更新するだけで、メモリの解放という処理の本体を後回しにすることができます。
つまり、メモリの解放というアクションについては逐次処理より早いのです。
後回しにした処理は、CPUに余裕があるときに行えばユーザーにストレスを与えることはありません。

ガーベージコレクションはメモリとCPUのトレードオフにおいて、メモリ負荷を上げることでCPU負荷を減らす仕組みです。
メモリが潤沢にある環境ではとても効果的ですが、メモリにもCPUにも限界がある組込機器との相性は、いいとは言えません。
GCは時々、目に見えて遅くなります。時には未処理のメモリが詰まってOutOfMemoryExceptionを出すことさえあります。
どちらもメモリ限界を超えてしまったときに起こります。組込ソフトウェアの宿命なのです。

なんで逐次処理にしてくれなかったんだろうと恨み事の一つも言いたくなる気持ちは分からないでもありませんが、考えてください。
もし逐次処理だったらその遅さはアプリの動作全体にまんべんなくかかります。
最新のAndroid端末でさえ十分に快適な処理速度を持っているとは言えません。
大げさに言うともし、Androidがガーベージコレクションを持つJavaを選択していなかったら、今の普及はなかったのではないでしょうか。

AndroidアプリがOutOfMemoryに陥るのは組込ソフトウェアだからです。GCだからではありません。
メモリマネージャがいるからと言ってメモリのことを考えなくていいわけではありません。
GCにはよいところもたくさんあります。
逐次管理なら起こらないだろう不思議な現象に悩まされることもあると思いますが、GCと仲良くすることで、よりよいシステムを構築することもまたできると思います。
GCと正しく付き合っていれば、GCのせいで起こる問題っていうのは、実はそんなに多くないのではないかと思っています。
仲良くしましょう。

2015/05/17

[Java][Android]メモリリーク

最初に断っておかなければならないことがあります。
私は組込機器とJVMに若干見識があるものの、AndroidもそのほかのJavaも業務として扱った経験は0に等しいエンジニアです。
Androidを生業にされている方々には是非、実経験に基づいたご意見をいただきたいと思っています。

Androidの開発者さんの間でごく自然に「メモリリークが…」という会話をされているのを聞くたびに違和感を感じています。
Javaには理論的にメモリリークは発生しないはずです。言語上でメモリの生成消滅の管理を行わないからです。
実際にAndroidの開発をしていると簡単にOutOfMemoryExceptionに見舞われるのは知っています。
Java界面から見て既に不要になっているメモリがその原因になっているときに、それを既知のシステムにならって「メモリリーク」と言いたくなる気持ちは分からないではありません。
しかし、それは「リーク(漏れ)」ではないはずです。

ネイティブ言語においてのメモリリークは、確保したメモリのハンドルへの参照を失うことで、当該メモリを操作するすべを失うことを言います。

void test(){
 void*p = malloc(BUFFER_SIZE);

// free(p);
}

free();に引き渡されることなくスコープから外れてしまったpに格納されていたアドレスは、C言語の界面で拾うことはもうできません。
ハンドルを失ったメモリは、言語の界面から操作することが不可能になります。メモリープールからこぼれ落ちたメモリの断片をリーク(漏れ)と呼んだのはまさに言い得て妙です。

一方Javaはガーベージコレクタを備えたメモリ管理機能を持つ言語です。
Javaのメモリマネージャは参照を失ったメモリ(Javaの場合Object)を定期的に整理することができます。
こうしてJavaは常に必要なサイズのメモリだけを保持します。

OutOfMemoryが発生する理由はシンプルです。OutOfMemorey(メモリ不足)になったからです。
Androidは概ね組込機器上で動作しますから、メモリに限界はあります。使い過ぎればOntOfMemoryとなります。これは当然です。

問題はOutOfMemoryの原因を探っていった時に、その原因が既に不要になったObjectに起因していた場合、メモリリークと呼びたくなる気持ちは分からないでもありません。
例えば、Listに情報を詰め込むようなロジックで、設計者が意図しないデータが蓄積してOutOfMemoryが発生するものをメモリリークの例に挙げているページがありましたが、それはバグです、リークと呼ぶのはJVMに対しての言いがかりだと思います。

自分が意図しない参照がもとでメモリが過剰に消費されることはいくらもあると思います。
例えばstaticでないインナークラスが暗黙で持っている親クラスの参照について意識していない人はそこそこいるかもしれません。
(余談ですが、C#はインナークラスに親クラスのプライベートにアクセスする権限を与えましたが、親クラスのインスタンスは明示的に引き渡さなければならないように設計されています。この設計はJavaの失敗(?)を踏まえてか良く練らていると感心しました。)
シングルトンにするべくstaticに保存したインスタンスもそう簡単に消えません。
Viewサブクラスが便利だからという理由だけで上位クラスのViewを保存してしまうと簡単に循環参照になります。
それらは未熟な設計者の意図を超えた寿命を持ちますが、それは設計者の頭からリークしているだけで、システム的にはリークしていません。

とても大きなリソースを消費してしまうので、GCで処理されるのを待っていることができないクラスは存在します。
Bitmapは典型的な例です。
画像を展開するためメガ単位のバッファを使います。
参照が切れてもGCが走るまでバッファを維持してしまうのは、組込機器上許されることではありません。
ですので、Bitmapにはrecycle()というメソッドが用意されています。
不要になったBitmapは速やかにrecycle()をコールしなければなりません。
もうひとつの例はファイルアクセスです。
JavaはGCがあるから明示的にclose()しなくても大丈夫、と思っていると痛い目にあいます。
C++をバックグラウンドに持っているエンジニア-私もそうですが-は、スコープを外れた刹那に暗黙的に処理をしてくれるデストラクタに慣れているので、最初にJavaにデストラクタがないことに驚愕し、finalize()が同じように動いてくれないことに失望します。
CからC++にグレードアップしたときに縁が切れたとおもっていたclose()と再び付き合わなければならなくなって、がっかりしたものです。
正直、Javaの設計段階に参照カウンタが0になった時に呼び出されるメソッドがあっても良かったのではないかと思っていますが、後の祭りです。(C#はusingという仕組みを持っています。これもまたうまく作ったものだと感心しています。)
いずれにしても、GCを待たずに後処理を行わなければならないクラスは存在します。OutOfMemoryの原因にはなり得ますが、リークではありません。

循環参照等でガーベージコレクションの効率が著しく低下することはあると思います。
その場合、参照が切れてからもGCに掛かるまでにそれなりの寿命を持つことは想像に難くありません。
しかし、それはまだ相応の時間があれば処理できる可能性があるもので、リークではありません。
相応の時間が取れず、ギブアップするJVMの実装もないとは言えません。それは現実問題を解決しなければならない組込Javaとして妥当な判断であると思います。
循環参照は設計で避けることができます。(循環参照を病的に避けようとする、ある種のプロセスはまた有害だと思うのですが…)
万が一循環参照が解決できないメモリマネージャを持ったJVMがあるとしたら、それはリークではなくJVMのバグです。

OutOfMemoryになりやすい状況をメモリリークと呼ぶんだよ、と言われればそうなのかもしれません。
それならそれは、あまりいい傾向ではないと思っています。
上であげた例はほぼすべて、正しい知識、特にメモリマネジメントの知識があれば回避できる問題です。
もうひとつ、Androidエンジニアの方々には、失礼かもしれませんが、組込ソフトウェアを作っているという意識が不足しているように思えてなりません。
スマートフォンは組込機器なのです。限られたリソースとパフォーマンスの上で以下に効率よくサービスを構築するかというのが技術なのです。
それはCPUやローメモリが見えていなくても変わりはありません。
バーチャルマシン上で実行されるプログラムですので、実装依存の部分に深入りできないという事情もあり、そこの部分は1つのCPU上で動けばOKなネイティブ実装より問題は複雑です。
「リーク」という言葉を聞くと、その言葉に逃げているように思えてしまうのです。

最初の言葉に戻りますが、私はJavaをかじっている組込屋です。
Android専門家の方々の声をお聞かせくだされば幸いです。

2015/05/10

[Java]シャッフルの話

面白い例外を出したので、話のタネに。

配列をシャッフルする必要がありました。
Arrays.shuffle()という関数があればそれを使って終わりだったのですが、残念ながらはCollectionsshuffle()はあってもArrays.shuffle()はありません。シャッフルのためだけにわざわざコレクションにデータを移しかえるのは無駄なので(この認識は間違いです。正解はページの最後でw)別の方法を模索しました。
別の理由でこの配列をsortする必要がありましたので、おなじくsort()を使ってシャッフルができないかと思い付き、以下のようなComparatorを作ってみました。

class Comparison implements Comparator<Player> {
 @Override
 public int compare(Player lhs, Player rhs) {
  return rand.int(3)-1;
 }
}

compare()は2つのオブジェクトを比較して、大小の判定をします。
通常はデータを解析して適切な判断をするのですが、このComparatorは結果を乱数で返します。
比較結果がめちゃくちゃなのですから、まっとうに並ぶわけがないですが、シャッフルしたいのですから狙い通りです。
これは一見上手くいきました。
一見上品にもみえました。

しかし、このロジックは稀に例外を起こします。

java.lang.IllegalArgumentException: Comparison method violates its general contract!

general contract、一般契約に違反しています、というもの。
この場合の一般契約とは、Java.lang.Objectのメソッド、equalsが同じものは常に同じであることを保証するというものを指すのだと思います。
equalsとhashCoedeはObjectのメソッド、Java世界のすべてのオブジェクトが備えていなければならないものです。
例えばこれらはHashMap等にオブジェクトに格納する際に使用されます。
一般契約が守られなければ、これらJavaの根幹に当たる仕組みの動作が保証されなくなります。
とても大事なルールなのです。

さて、今回私はObjectをいじっているわけではありませんが、Objectが一般規約を守っているという前提で使用されているsortという仕組みを使っています。
どのようにして私のハッキングに気がついたのか定かではありませんが、恐らくはsortのロジックがあり得ない状態に陥ったのは想像に難くありません。
この仕掛けはやってはいけないものでした。

この話の怖いところは、上記の例外が以外と低確率でしかでないということです。
件のアプリでは、データ8つの抽選で遭遇することはまずなくて、80個程度の抽選で時々出るというくらいでした。
この致命的なバグは、試験が甘ければ気付かれることなく製品に入り込む可能性が十分にあります。

では、当初の命題、配列をシャッフルするにはどうするのが正しいのでしょうか。
今、私は以下のロジックを使っています。

Collections.shuffle(Arrays.asList(players));

別の記事で書きましたが、asListはplayersをラップする固定長のリストを返します。asListで返されたリストの内容を操作するとplayersの内容も操作されることが仕様上保証されています。シャッフルの場合ももちろんplayersのインスタンス内容が直接シャッフルされます。
リスト化する際にコピーが発生しませんから、配列をシャッフルするのとほぼ等価なパフォーマンスが期待できます。

ところで、こんな仕掛けを思いついてしまいました。

class Comparison implements Comparator<Player> {
 @Override
 public int compare(Player lhs, Player rhs) {
  if(lhs==rhs){
   return 0;
  }else{
   return rand.int(2)==0?1:-1;
  }
 }
}

同じインスタンスなら0(同じ)を返し、それ以外は0以外をランダムで返します。
比較は乱数ですが、equalsについての動作は保証されています。
まぁ、単なる興味で、使う気はありませんが。

[Android][Java]ArrayAdapterはアダプター

AndroidにはArrayAdapterというクラスがあります。
Adapterの実装の一つで、ListViewやSpinnerを使ったUIを実装する際には必須といえるクラスなので、触ったことがないという人は少ないと思います。
しかし、一方Adapterという非常に抽象的なネーミングだったり、文字列を表示するだけならカスタマイズの必要がなかったりして、サンプルコードをコピペしたまま良くわからずに使っているという人も多いのではないかと思います。
実は私もそうでした。
全容がわかっていなくても、表面上に出てくるところをいじっていると目前の目的が達成されるので、ついつい疑問を後回しにしていました。

このクラスを使っていると、不可解な現象に遭遇します。
前述のasListと同じく、追加ができる場合と例外が発生する場合があるのです。
個の現象について、仕様はなにも語っていません。
いろいろ整理していくと初期化の違いが影響しているらしいということが分かりました。

ArrayAdapterはいくつかのコンストラクタを持っています。

ArrayAdapter(Context context, int resource)// 1
ArrayAdapter(Context context, int resource, int textViewResourceId)// 2
ArrayAdapter(Context context, int resource, T[] objects)// 3
ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)// 4
ArrayAdapter(Context context, int resource, List<T> objects)// 5
ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)// 6

1,2は要素を初期化しないコンストラクタ、3,4は初期値を配列、5,6はリストでそれぞれ提供するものです
下はそれぞれを動作確認するためのコードです。

private void test(Context context){
    String[]labels = {"test1", "test2", "test3"};
    ArrayList<String>array = new ArrayList<String>();
    for(String label : labels){
        array.add(label);
    }

    ArrayAdapter<String> adapter = ArrayAdapter<String>(context, 0);
    adapter.add("test4");//7

    adapter = ArrayAdapter<String>(context, 0, labels);
    adapter.add("test4");// 8

    adapter = ArrayAdapter<String>(context, 0, Arrays.asList(labels));
    adapter.add("test4");// 9

    adapter = ArrayAdapter<String>(context, 0, array);
    adapter.add("test4");// 10
}

このコードを実行すると7,10は成功し、8,9は例外が発生します。
この振る舞いは仕様に特に記述されていません。
この違いがなぜ起こるのか、しばらく疑問でした。

実は答えは簡単でした。
ArrayAdapterは外部にあるデータソースをGUIが操作できるインターフェイスに適合(Adapt)させるアダプターなのです。
私の誤認識はArrayAdapterが実装形式として内部に配列(Array)を持っているアダプタ(Adapter)だと思い込んでいたところにありました。
言い訳を許してもらえるなら、ArrayAdapterでなくCollectionAdapterだったなら、この誤解はなかったでしょう。
ArrayAdapterがアダプトするのはArrayだけではありません。ArrayListだけでもなく、すべてのコレクションを対象にしているのですから。

もうひとつ混乱を引き起こしたのは1,2のコンストラクタです。
こちらはデータソースのオリジナルがない状態を想定したコンストラクタだと思われます。つまり以下のように書くことができます。

ArrayAdapter<String>(this, 0, 0, new ArrayList<String>());

親切ではありますが、Adapterとしては過剰サービスだったのではないでしょうか。

ArrayListener.add()はデータソースのadd()をデリゲートしています。ArrayAdapterがデータの変更タイミングを察知するために必要なデリゲーションです。
こちらを通さない変更は都度notifyDataSetChanged()で知らせてやる必要があるのです。
構造が分かればいろいろなことがクリアになります。
実装ではありません、構造です。