Pebble プラットフォーム用のアプリの作成を始めたばかりでも、より複雑なアプリを作成している場合でも、アプリログからの出力は、アプリのコードの問題を追跡するのに非常に役立ちます。一般的な問題のいくつかの例をここで探ります。コンパイラ出力に慣れるためのいくつかの例も含まれています。
書かれたコードの構文エラー(Common Syntax Errors を参照)とは対照的に、アプリが実際に Pebble 上で実行されたときにのみ発生する問題もあります。これは、完全に有効な C コードが、ハードウェアと互換性のない不適切な動作を引き起こす場合があるためです。
これらの問題は、アプリがクラッシュし、原因に関するその他の情報がほとんど利用できないという形で現れる可能性があり、これは、診断と修正に異常に長い時間がかかる可能性があることを意味します。
問題のある行を追跡するのに役立つオプションの 1 つは、アプリの初期化の開始時から始めて、APP_LOG() への呼び出しを使用して、実行がどこで停止するかを確認することです。ログ呼び出しのメッセージが表示される場合、実行は少なくともその時点に達しています。アプリがクラッシュするポイントの後になるため、表示されなくなるまで、論理実行を通じて呼び出しをさらに移動します。
Pebble SDK は動的メモリ割り当てモデルを使用しています。つまり、開発者が使用できるすべての SDK オブジェクトと構造は、必要に応じて割り当てられます。このモデルには、必要なデータとオブジェクトのみをメモリに保持し、必要でないときにアンロードできるという利点があり、作成できるアプリの規模と機能が向上します。
このパラダイムでは、構造は最初にポインタとして宣言され(NULL の初期値が与えられる場合があります)、アプリの初期化の後半で完全に構造が割り当てられます。したがって、発生する可能性のある最も一般的な問題の 1 つは、開発者が未割り当ての構造またはデータ項目を使用しようとすることです。
たとえば、次のコードセグメントはクラッシュを引き起こします:
Window *main_window;
static void init() {
// Attempting to push an uninitialized Window!
window_stack_push(main_window, true);
}
コンパイラはこれを報告しませんが、実行すると、Window が表示される前にアプリがクラッシュし、次のようなエラーメッセージがコンソール出力に送信されます:
[INFO ] E ault_handling.c:77 App fault! {f23aecb8-bdb5-4d6b-b270-602a1940575e} PC: 0x8016716 LR: 0x8016713
[WARNING ] Program Counter (PC): 0x8016716 ???
[WARNING ] Link Register (LR): 0x8016713 ???
可能な場合、pebble ツールは、クラッシュ時の PC(プログラムカウンタ、または現在実行されているステートメント)と LR(リンクレジスタ、現在の関数スコープが終了したときに戻るアドレス)のアドレスと行番号を開発者に通知します。これは、問題の原因を示すのに役立つ場合があります。
この問題は、ポインタとして宣言されたデータ構造が、引数として使用される前に、適切な _create() SDK 関数を使用して適切に割り当てられるようにすることで修正できます:
Window *main_window;
static void init(void) {
main_window = window_create();
window_stack_push(main_window, true);
}
使用可能なヒープスペースが制限されている状況では、_create() 関数は NULL を返す可能性があり、オブジェクトは割り当てられません。アプリは次のようにこの状況を検出できます:
Window *main_window;
static void init(void) {
main_window = window_create();
if(main_window != NULL) {
// Allocation was successful!
window_stack_push(main_window, true);
} else {
// The Window could not be allocated!
// Tell the user that the operation could not be completed
text_layer_set_text(s_output_layer,
"Unable to use this feature at the moment.");
}
}
このNULLポインタエラーは、SDK 外で開発者が独自に作成した動的に割り当てられた構造体や変数でも発生する可能性があります。例えば、典型的な動的割り当て配列は、割り当て前に使用するとクラッシュを引き起こします:
char *array;
// Array is still NULL!
array[0] = 'a';
この問題は、配列が使用される前に適切に割り当てられることを確認することで、以前と同様の方法で修正できます:
char *array = (char*)malloc(8 * sizeof(char));
array[0] = 'a';
上記の window_create() について述べたように、malloc() の戻り値もチェックして、要求されたメモリ割り当てが正常に完了したかどうかを判断してください:
array = (char*)malloc(8 * sizeof(char));
// Check the malloc() was successful
if(array != NULL) {
array[0] = 'a';
} else {
// Gracefully handle the failed situation
}
コンパイラには問題なく見えても、ランタイムでエラーを引き起こす可能性のあるもう 1 つの問題は、配列の境界外の配列インデックスにアクセスしようとするなど、配列の不適切な使用です。これは、配列を反復処理するようにループが設定されているが、配列のサイズが縮小されたり、ループ条件が変更されたりした場合に発生する可能性があります。
たとえば、以下の配列反復はクラッシュを引き起こしませんが、プログラムを脆弱にし、これらの数値が変更されたときにエラーが発生しやすくする「マジックナンバー」の使用が含まれています:
int *array;
static void init(void) {
array = (int*)malloc(8 * sizeof(int));
for(int i = 0; i < 8; i++) {
array[i] = i * i;
}
}
割り当てられた配列のサイズが縮小されると、反復ループが配列の境界外に出たときにアプリがクラッシュします:
int *array;
static void init(void) {
array = (int*)malloc(4 * sizeof(int));
for(int i = 0; i < 8; i++) {
array[i] = i * i;
// Crash when i == 4!
}
}
ループの反復回数は配列のサイズにリンクされているため、この問題は、配列のサイズを事前に 1 か所で定義し、配列のサイズが必要なすべての場所でその値を使用することで回避できます:
#define ARRAY_SIZE 4
int *array;
static void init(void) {
array = (int*)malloc(ARRAY_SIZE * sizeof(int));
for(int i = 0; i < ARRAY_SIZE; i++) {
array[i] = i * i;
}
}
上記の代替ソリューションは、ARRAY_LENGTH() マクロまたは sizeof() 関数のいずれかを使用して、ループする配列のサイズをプログラムで決定することです。