MSC19-C. 配列を返す関数は、NULL 値ではなく空の配列を返すこと
関数は配列をそのサイズに基づいて返す場合がある。返り値がサイズ 0 の配列となる場合に、NULL を使用するべきではない。返り値は空の配列とし、呼び出し元の関数が返り値を正しく処理できるようにする必要がある。
C 言語は配列のサイズを管理しないが、サイズ管理の動作を模倣する 2 つの一般的な方法が使われている。1 つは、サイズを格納した整数フィールドとともに構造体内に配列をラップすることである。もう 1 つは、配列のデータの最後に標識値を配置することである。この 2 番目の方法は、null 終端バイト文字列(NTBS)という形で最も一般的に使われる方法である。
違反コード (構造体)
以下の違反コードの在庫管理システムでは、管理している品目数を length として記録している。各品目毎の在庫数を配列で管理しており、各エントリの値が対応する品目の在庫数である。新しい品目を追加すると、構造体内の length が増える。1 つの品目の在庫を増やすと、その品目に対応する配列のエントリの値が増える。たとえば、5 冊の本と 2 個の消しゴムが在庫にあり、本のインデックスが 0 で、消しゴムのインデックスが 1 ならば、stockOfItem[0] = 5 および stockOfItem[1] = 2 となる。
問題は、品目の在庫がまったくない場合に生じる。getStock は length = 0 を認識して NULL を返す。以下のコード例では、getStock が NULL の値を返した場合に main がその値を検査しないことが原因で誤った動作が発生する。その結果、main 関数に戻ったあとでプログラムが異常終了する。
#include <stdio.h> enum { INV_SIZE=20 }; typedef struct { size_t stockOfItem[INV_SIZE]; size_t length; } Inventory; size_t *getStock(Inventory iv); int main(void) { Inventory iv; size_t *item; iv.length = 0; /* インベントリを変更する可能性があるが、終了時に品目を残さない、その他のコード */ item = getStock(iv); printf("Stock of first item in inventory: %d\n", item[0]); return 0; } size_t *getStock(Inventory iv) { if (iv.length == 0) { return NULL; } else { return iv.stockOfItem; } }
適合コード
以下の適合コードは NULL を返さず、サイズが 0 であっても単に配列 item を返す。main 関数は誤った動作をせず、この状況に適切に対処することができる。
#include <stdio.h> enum { INV_SIZE=20 }; typedef struct { size_t stockOfItem[INV_SIZE]; size_t length; } Inventory; size_t *getStock(Inventory iv); int main(void) { Inventory iv; size_t i; size_t *item; iv.length = 0; /* インベントリを変更する可能性があるが、終了時に品目を残さない、その他のコード */ item = getStock(iv); if (iv.length != 0) { printf("Stock of first item in inventory: %d\n", item[0]); } return 0; } size_t *getStock(Inventory iv) { return iv.stockOfItem; }
違反コード (標識値)
以下のコード例は前述したものに類似した在庫管理システムを実装しているが、構造体内に配列のサイズを格納する代わりに、FINAL_ITEM を標識値として使用する。最後の品目の次のエントリに FINAL_ITEM を設定する。在庫切れの品目(値が0となる)は配列から削除され、それ以降の品目の内容が下位インデックスにシフトされることを想定している。
この違反コードでは、在庫がある品目の配列を、各在庫品目の量で並べ替えて返そうとする。arraySort 関数は、在庫されている品目がない場合に、空の配列へのポインタではなく NULL を返すという誤った動作をしている。main 関数は、返された配列の内容を印刷しようとしてこれを間違って処理してしまう。その結果、プログラムの異常終了が発生する。
#include <stdio.h> #include <stdint.h> #include <malloc.h> enum { FINAL_ITEM=SIZE_MAX, INV_SIZE=20 }; size_t *arraySort(size_t *array); int main(void) { size_t i; size_t stockOfItem[INV_SIZE]; size_t *sortedArray; /* 在庫の配列を使用する可能性はあるが空にして終了するその他のコード */ sortedArray = arraySort(stockOfItem); for (i = 0; sortedArray[i] != FINAL_ITEM; i++) { printf("Item stock: %d", sortedArray[i]); } return 0; } /* 新しくソートされた配列を作成 */ size_t *arraySort(size_t *array) { size_t i; size_t *sortedArray; for(i = 0; array[i] != FINAL_ITEM; i++); if (i == 0) { return NULL; } sortedArray = (size_t*) malloc(sizeof(size_t)*i); if (sortedArray == NULL) { /* メモリエラーの場合の処理 */ } /* ソートされたデータを配列に追加 */ return sortedArray; }
適合コード (標識値)
以下の適合コードでは sortedArray 関数が空の配列を正しく返す。配列のサイズが 0 の場合は、sortedArray がサイズ 1 の配列を割り当て、標識値を設定する。こうすることで、その配列を呼び出し側の関数に正しく返すことができる。
#include <stdio.h> #include <stdint.h> #include <malloc.h> enum { FINAL_ITEM=SIZE_MAX, INV_SIZE=20 }; size_t *arraySort(size_t *array); int main(void) { size_t i; size_t stockOfItem[INV_SIZE]; size_t *sortedArray; /* 在庫の配列を使用する可能性はあるが空にして終了するその他のコード */ sortedArray = arraySort(stockOfItem); for (i = 0; sortedArray[i] != FINAL_ITEM; i++) { printf("Item stock: %d", sortedArray[i]); } return 0; } /* 新しくソートされた配列を作成 */ size_t *arraySort(size_t *array) { size_t i; size_t *sortedArray; for(i = 0; array[i] != FINAL_ITEM; i++); if (i == 0) { size_t *emptyArray = (size_t*) malloc(sizeof(size_t)); if(emptyArray == NULL) { /* メモリエラーの場合の処理 */ } emptyArray[0] = FINAL_ITEM; return emptyArray; } sortedArray = (size_t*) malloc(sizeof(size_t)*i); if (sortedArray == NULL) { /* メモリエラーの場合の処理 */ } /* ソートされたデータを配列に追加 */ return sortedArray; }
リスク評価
サイズ 0 の配列の代わりに NULL を返すと、呼び出し側が NULL を正しく処理しなかった場合に脆弱性につながる可能性がある。呼び出し側の関数が NULL に対する操作を行うと、プログラムの異常終了を引き起こすことがある。
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
MSC19-C |
低 |
低 |
高 |
P1 |
L3 |
参考情報
- [Bloch 08] Item 43: return empty arrays or collections, not nulls
翻訳元
これは以下のページを翻訳したものです。
MSC19-C. For functions that return an array, prefer returning an empty array over a null value (revision 23)