TOP >> マニアックなプログラミング
トリッキーコードネット トリッキーなコード

バグの温床(バグを生む原因)となるマクロ (C言語)

C言語でマクロを使用する際の注意点(気をつけないと予期せぬバグ発生の原因となりえる)について書きます。

まずは、以下のコードを見てください。
1 : #include <stdio.h> 2 : 3 : #define CUBE(NUM) (NUM * NUM * NUM) 4 : 5 : int main(void) 6 : { 7 : int num = 4; 8 : 9 : printf("%d の三乗は %d\n", num, CUBE(num)); 10 : 11 : return 0; 12 : }
結果) バグのあるC言語マクロ 3行目で定義しているCUBEマクロは、その名の示すとおり三乗を求めるマクロです。 一見正常に動作している様に見えますが、このマクロ、実はバグの温床になっています。 以下のコードを見てください。(※ 冒頭のコードの一部を修正したものです)
1 : #include <stdio.h> 2 : 3 : #define CUBE(NUM) (NUM * NUM * NUM) 4 : 5 : int main(void) 6 : { 7 : printf("%d の三乗は %d\n", 10 + 20, CUBE(10 + 20) ); 8 : 9 : return 0; 10 : }
結果) バグが発生したC言語マクロ 30の三乗という事で、 30 x 30 x 30 = 27000 が表示されるかと思いきや、430が表示されてしまいました。 この理由は、CUBEマクロを展開してみれば直ぐに分かります。 CUBE(10 + 20) ⇒ (10 + 20 * 10 + 20 * 10 + 20) ⇒ (10 + 200 + 200 + 20) ⇒ 430 演算子には優先順位が有る為、 このように 「値を計算するマクロ中に、数値と演算子のセットを入れると、予期せぬバグを引き起こす」 可能性があります。 これを防止する為には、「マクロへ演算子を渡さない」という鉄の掟を作るか(← 笑)、以下の様にマクロを修正します。
#define CUBE(NUM) ((NUM) * (NUM) * (NUM))
つまり、マクロに渡された値を、括弧で囲むという事です。
1 : #include <stdio.h> 2 : 3 : #define CUBE(NUM) ((NUM) * (NUM) * (NUM)) 4 : 5 : int main(void) 6 : { 7 : printf("%d の三乗は %d\n", 10 + 20, CUBE(10 + 20) ); 8 : 9 : return 0; 10 : }
結果) バグを修正したC言語マクロ はい、一件落着~~♪ ・・・と思いきや、実はこの、値を括弧で囲ったマクロでも、バグの温床になっています。
1 : #include <stdio.h> 2 : 3 : #define CUBE(NUM) ((NUM) * (NUM) * (NUM)) 4 : 5 : int main(void) 6 : { 7 : int i = 2; 8 : 9 : printf("%d の三乗は %d\n", i, CUBE(++i) ); 10 : 11 : return 0; 12 : }
結果) バグが発生したC言語マクロ 9行目のコードを展開すると、以下の様になります。
printf("%d の三乗は %d\n", i, ((++i) * (++i) * (++i)) );
副作用完了点までの評価順序は未定義の為、この場合の結果は保証されません。 つまり、処理系によって結果が異なる可能性があるという事です orz では、この場合にどうすれば良いのかというと、(マクロからは遠ざかってしまいますが)インライン関数を使用します。
1 : #include <stdio.h> 2 : 3 : inline int cube(int num) 4 : { 5 : return num * num * num; 6 : } 7 : 8 : int main(void) 9 : { 10 : int i = 2; 11 : 12 : printf("%d の三乗は %d\n", i, cube(++i) ); 13 : 14 : return 0; 15 : }
結果) C言語マクロとインライン関数 ・・・まぁ、インライン関数って 開発環境によっては使用できなかったりするんですがね^^;) とりあえず今回はここまで♪
トリッキーコードネット の TOPへ HOTNEWS の 総合TOPへ