はじめに
大学の授業でC言語を使う必要があるのですが、配列の境界チェックがないことが非常に辛かったため、マクロを書きました。
全ての[]
演算子や単項*
演算子についてチェックしておくと、範囲外アクセスを気にしなくてよくなるためストレスが減りました。
プログラミング初心者ですので、何か誤りや改善点等ございましたらご指摘いただけると幸いです。
コード
一次元配列用
//#define NDEBUG
#include <assert.h>
/*
使用例:
RANGE_CHECK_1D(a, 5);
a[5];
*/
#define RANGE_CHECK_1D(array, i) \
do { \
assert(0 <= (i)); \
assert((long unsigned int)(i) < (sizeof(array) / sizeof(*array))); \
} while(0)
二次元配列用
//#define NDEBUG
#include <assert.h>
/*
使用例:
RANGE_CHECK_2D(a, 3, 4);
a[3][4];
*/
#define RANGE_CHECK_2D(array, i, j) \
do { \
assert(0 <= (i)); \
assert((long unsigned int)(i) < (sizeof(array) / sizeof(*array))); \
assert(0 <= (j)); \
assert((long unsigned int)(j) < (sizeof(*array) / sizeof(**array))); \
} while(0)
ポインタ用
関数の引数などで配列を渡すとsizeof()によって配列のサイズを調べられなくなるため、ポインタ用も用意しました。最大インデックスは人力入力です。これを使って新しいマクロを定義して使うこともできます。
//#define NDEBUG
#include <assert.h>
#define RANGE_CHECK_1D_P(n, i) \
do { \
assert(0 <= (i)); \
assert((i) < (n)); \
} while(0)
使用例
[]
演算子や単項*
演算子などでアクセスする直前に置いて使います。
デバッグ後も削除しない場合#define NDEBUG
と書かないと速度が低下してしまうため注意してください。
#include <stdio.h>
int main(void) {
int a[10] = {}, b[3][5] = {}, i, j;
for(i = 0; i < 10; i++) {
RANGE_CHECK_1D(a, i);
printf("%d\n", a[i]);
}
for(i = 0; i < 3; i++) {
for(j = 0; j < 6; j++) {
RANGE_CHECK_2D(b, i, j);
printf("%d\n", b[i][j]);
}
}
}
参考サイト
https://www.jpcert.or.jp/sc-rules/c-pre10-c.html
https://qiita.com/SaitoAtsushi/items/ee17466c464fb7a270d2
編集履歴
2020/07/27
@fujitanozomu さんよりご指摘をいただき、インデックス最大値との比較時にキャストを追加しました