メインコンテンツ

安全でないマクロへの引数の二次的影響

複数回評価されるか、評価されない可能性のある引数が含まれたマクロ

説明

この欠陥は、二次的影響のある式を含む安全でないマクロを呼び出した場合に発生します。

  • 安全でないマクロ: 展開時に、安全でないマクロは引数を複数回評価したり、まったく評価しなかったりします。

    たとえば、ABS マクロは引数 x を 2 回評価します。

    #define ABS(x) (((x) < 0) ? -(x) : (x))

  • 二次的影響: 評価されると、二次的影響のある式はその式内の 1 つ以上の変数を変更します。

    たとえば、++nn を変更しますが、n+1n を変更しません。

    チェッカーは、入れ子にされたマクロ内の二次的影響を考慮しません。また、チェッカーは関数呼び出しや volatile 変数アクセスを二次的影響と見なしません。

リスク

二次的影響のある式を含む安全でないマクロを呼び出すと、その式は複数回評価されたり、まったく評価されなかったりします。二次的影響は複数回発生する場合もあれば、一切発生しない場合もあり、予期しない動作の原因になります。

たとえば、呼び出し MACRO(++n) では、変数 n が 1 回のみインクリメントされることを想定します。MACRO が安全でないマクロの場合、インクリメントは複数回発生したり、まったく発生しなかったりします。

デバッグ モード以外では assert マクロが無効にされるため、チェッカーは、assert マクロ内の二次的影響を含む式にはフラグを設定します。デバッグ モード以外でコンパイルするには、コンパイル中に NDEBUG マクロを定義します。たとえば、GCC でフラグ -DNDEBUG を使用します。

修正方法

別のステートメントで二次的影響のある式を評価してから、その結果をマクロ引数として使用します。

たとえば、以下のようにはしません。

MACRO(++n);
次の 2 ステップで演算を実行します。
++n;
MACRO(n);
または、マクロの代わりにインライン関数を使用します。二次的影響のある式を引数としてインライン関数に渡します。

チェッカーは、マクロ本体のブロック スコープ内でのみ定義されたローカル変数の変更を二次的影響と見なします。この欠陥は、変数がマクロ本体内でのみ可視であるため発生しません。この種類の欠陥が表示された場合は、欠陥を無視します。

すべて展開する

#define ABS(x) (((x) < 0) ? -(x) : (x))
  
void func(int n) {
  /* Validate that n is within the desired range */
  int m = ABS(++n);
 
  /* ... */
}

この例では、ABS マクロは引数を 2 回評価します。2 回目の評価は意図しないインクリメントになる可能性があります。

修正 — マクロの使用と式の評価を分離

1 つの修正方法として、最初にインクリメントを実行してから、その結果をマクロに渡します。

#define ABS(x) (((x) < 0) ? -(x) : (x))
  
void func(int n) {
  /* Validate that n is within the desired range */
  ++n;
  int m = ABS(n);
 
  /* ... */
}
修正 — インライン関数で式を評価

別の修正方法として、インライン関数で式を評価します。

static inline int iabs(int x) {
  return (((x) < 0) ? -(x) : (x));
}
  
void func(int n) {
  /* Validate that n is within the desired range */
 
int m = iabs(++n);
 
  /* ... */
}

結果情報

グループ: プログラミング
言語: C | C++
既定値: オフ
コマンド ライン構文: SIDE_EFFECT_IN_UNSAFE_MACRO_ARG
影響度: Medium

バージョン履歴

R2018b で導入