quaternionの日記: C言語で変数をスワップするマクロ 27
日記 by
quaternion
H31春基本情報技術者試験問9(C言語)[PDF]で,変数をスワップするマクロが使われていた.本文中には
Swap(x, y) は x と y の内容を入れ替えるために用意したマクロである
とだけ書かれていて,その実装は与えられていない.
このようなSwapマクロはどのように書くのが正解だろうか?
H31春基本情報技術者試験問9(C言語)[PDF]で,変数をスワップするマクロが使われていた.本文中には
Swap(x, y) は x と y の内容を入れ替えるために用意したマクロである
とだけ書かれていて,その実装は与えられていない.
このようなSwapマクロはどのように書くのが正解だろうか?
人生unstable -- あるハッカー
整数型なら (スコア:2)
排他論理和の3回ですむのですが、浮動小数点やポインタが。
do - while(0)で囲む (スコア:1)
#define Swap(x, y) do { int z = y; y = x; x = z; } while (0)
のようにdo - while (0)で囲むという方法もあります。
Re:do - while(0)で囲む (スコア:2)
Re:do - while(0)で囲む (スコア:2)
問題文を読むとintもlongも渡されることが明示してあったため,intはまずかったです.リンク先は訂正しました.しかしlongにしたところで汎用性が上がるわけではないですしね...
Re:do - while(0)で囲む (スコア:1)
> ビット演算で移動する方法
x ^= y;
y ^= x;
x ^= y;
ですね。
複合代入演算子を使わない場合は、こう書き下せます(XORは可換なので)
x = x ^ y;
y = x ^ y;
x = x ^ y;
敢えて中間変数を明示するならば、(このトリックを使うメリットが無くなりますが)
temp = x ^ y;
y = temp ^ y;
x = temp ^ x;
ということですね。
野暮だけど… Re:do - while(0)で囲む) (スコア:2)
Re: (スコア:0)
そしてSwap(x, x)で両方ともゼロになる。Swap(x, x)ではわざとらしすぎて誰もやらんだろと思うだろうが、実際に配列要素の操作に使うときたまたま同じ要素を指していないと保証することはそんなに自明ではない。
Re:do - while(0)で囲む (スコア:1)
事前判定を入れると、ますます利点が薄まりますしね。
素直に中間変数使って交換しとけという話ですね。
関数 (スコア:0)
xとyが同じものだったりしたときに不安を覚える。
なのでswap関数を用意するのが正解だと思う。
これによってSwapマクロはswap関数を呼び出す極めてシンプルなものになる!
Re:関数 (スコア:2)
C言語でswap関数を書けますか?
Re: (スコア:0)
void swap(int *x, int *y) { int z = *x; *x = *y; *y = z; }
#define Swap(x, y) swap(&(x), &(y))
これならint以外や右辺値を渡したときもちゃんとエラーになる
Re:関数 (スコア:2)
Re: (スコア:0)
まず一つ訂正。C言語ではポインターを異なる型へのポインターに変換してもエラーにはならなかった(規格で明示的に認められている)。アライメント要求に違反すると未定義動作になるし多くの場合アクセスした瞬間未定義動作になるしコンパイラーによっては警告を出すかもしれないけど。
で、考えた結果こうなった。
void swap(unsigned char *x, unsigned char *y, size_t s)
{
for (size_t i = 0; i < s; i++) {
unsigned char z = x[i];
x[i] = y[i];
y[i] = z;
Re:関数 (スコア:2)
これはお見事.
Re: (スコア:0)
ここにぶら下げよう。
前提についての質問ですけど、型の限定って出来ましたっけ?
単に内容を入れ替えるマクロだと、違う型が指定されたり
想定している型(int)以外が指定されたり。
親コメの言う通りswap関数用意した方が良さそう。
Re:関数 (スコア:2)
問題文に明示されているのはint型変数の交換とlong型変数の交換の2種類だけです.
Re: (スコア:0)
C11なら_Generics使って何とか。実装は引数の型に応じて関数を呼び分ける感じになると思うけど
正解はこれだ (スコア:0)
・そのようなマクロは書くべきではないし、書こうと考えてもいけない
・問題の理解のために必要であれば、必要最低限の実装でよい
Re: (スコア:0)
俺の言うことを守らないと、仕事や教育そっちのけでパズルに走る無能のカスになるよ
Re: (スコア:0)
その方向性を突き詰めたら
・C言語でプログラムを書くべきではないし、書こうと考えてもいけない
・基本情報の言語の選択はCASLマジオススメ
ってなると思う
無難に書くならこんなとこかなー (スコア:0)
//xとyの値を入れ替える。xとyの型は同じでなければならない
#define Swap(x,y){
unsigned int __i = 0;
unsigned char __t;
void *p1 = (void*)&x;
void *p2 = (void*)&y;
while(i<size_t(x)){
__t = ((unsigned char*)&x)[__i];
((unsigned char*)&x)[__i] = ((unsigned char*)&y)[__i];
((unsigned char*)&y)[__i] = __t;
__i++;
}
}
Re: (スコア:0)
リンク先今読んだ(^^;
do{}while(0);で囲めば{}省略ifの中で使われても大丈夫じゃないかな...
Re: (スコア:0)
__iや__tのようなアンダースコア2つを含む名前を使ってはいけない。標準ライブラリヘッダーでは使っているかもしれないが、それはまさに標準ライブラリヘッダーなどが使うために予約されているからであって、ユーザープログラムで使い出したら台無しになる。
Re: (スコア:0)
失礼、どこであれ含んではいけないのはC++だった。C言語ではアンダースコア2つが予約されているのは先頭に来るときだけだった(つまり結局ダメ)。
Re: (スコア:0)
> while(i<size_t(x)){
もしかして: sizeof(x)
Re: (スコア:0)
Swap(*p++, y)とかやったらヤバくない?
誰も指摘してないけど、引数は全て括弧で括る事を習慣づけよう。 (スコア:0)
多分下記が最低ライン。
#define Swap(x, y) do{(x)^=(y); (y)^=(x); (x)^=(y);}while(0)
そもそもコード規約でdefineマクロを縛ってる所も有りますね。
複文になった時点で、関数化がデフォルトで基本マクロは不可。
どうしても書く場合は落とし穴に詳しい人のコードレビューを通らないとダメ。
ちなみに、引数を括弧で括ってないのは全てダメ。
Swap(x+1,y);みたいなアホな事されたときにエラーにならないかもしれない。
どっちにしろ、Swap(++x,y)みたいなのには無力なので関数にしましょう。
結論:単なる文字置換なので複雑な事(複文が必要な事)をしたらアカン。