#define swap(type,a,b) do { type t; t=a; a=b; b=t; } while(0)
というのが一般的な方法です。(※)
このswapマクロは、様々な型に適用できるので非常に便利に思えます(ついでに 普通の関数として実装したswapのようにポインタを渡す必要もありません)。ほ とんどの場合で、このマクロは正しく動作します。
しかし、次のようなコードを考えてみましょう。
int a[5] = { 0, 0, 0, 0, 0 };
int i = 3;
swap(int,i,a[i]);
4番目の配列要素 a[3](= 0) と インデックスi(= 3)を入れ換えます。その結果、
i = 0, a[3] = 3になると予想されます。ところが実際にはそうなりません。
関数マクロの引数は遅延評価されてしまうからです。
マクロの本体が展開される時、引数の名前がそのまま本体の仮引数と置き換えら れます(これを名前呼出し(call by name)と呼びます)。さきほどの例ですと、 swapマクロは次のように展開されます。(見やすくするために適当に整形してい ます。)
1 do {
2 int t;
3
4 t = i;
5 i = a[i];
6 a[i] = t;
7
8 } while (0)
5行目の i = a[i] で、iに0が代入されてしまうので、6行目は、a[0] = 3 となっ
てしまいます。結局、swap(int,i,a[i])は正しく動作しないのです。
(swap(int,a[i],i)は正しい結果になります。)関数形式のマクロ(あるいは名前呼
出し機構を持った関数)を使って、swap関数を正しく実装することは不可能なこと
が知られています。
マクロ関数の危険性については、たとえば、#define sqrt(x) ((x)*(x))と定義し た時の、sqrt(n++);や、#define sqrt(x) x*x としたときの、sqrt(n+n); のような問題はよく知られていますが、ここで挙げたswapの場合はあまり取り挙げ られない話題のような気がします。プログラミング関係のウェブページを眺めてい ると、結構swapマクロを注意なしに紹介しているので気にかかるところです。
まあ、swap(i,a[i])を使う場面なんてあまり考えられませんので気にすることも無 いと思うかもしれませんが、このように正しく動かないことがあり得るということ は頭の片隅に入れておきましょう。
|
(※) 本体が、
{ type t; t=a; a=b; b=t; } /* --- (1) */
でなく
do { type t; t=a; a=b; b=t; } while(0)
と余計なdo〜whileで囲まれているのは、以下の場合に対処するためです。
if (exp)
swap(int, x, y);
else
swap(int, x, z);
この場合、(1)のようにマクロが定義されていると、
if (exp)
{ int t; t=x; x=y; y=t; };
else
{ int t; t=x; x=z; z=t; };
と展開されてしまい、文法エラーになってしまいます。
(elseが浮いてしまいます。)
|
#define swap(a,b) (a ^= b, b ^= a, a ^= b)この方法だと、余計な一時変数を使わずに値を交換することができます。ただし く値が交換できるかどうか確認してみましょう。
a b ----------------------------------- step1 a^b b (a ^= b) step2 a^b b^(a^b) = a (b ^= a) step3 (a^b)^a = b a (a ^= b)たしかに、変数aとbの値が交換されます。しかし、swap(a,a)としたらどうなる でしょうか?
a b ----------------------------------- step1 a^a = 0 0 (a ^= b) step2 0 0^0 = a (b ^= a) step3 0^0 = 0 0 (a ^= b)このように、途中で値が0になってしまいまい、元の値が消滅してしまいます。