Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

##演算の結果

タグ: C++
#include <iostream>
#define CALL(arg) CALL_ ## arg
#define CALL_(x, y) #x " fly out of your " #y
#define CALL_nullpo "gatt"

int main()
{
	std::cout << CALL((demons, nose)) << std::endl;
	std::cout << CALL(nullpo) << std::endl;
}

このコードはVC8では

demons fly out of your nose
gatt

このようにいい具合に動作するが、gcc 4.0では

error: passing "CALL_" and "(" does not give a valid preprocessing token

とエラーが出てコンパイルできない。

Cの規格によると、##演算子の挙動は

置換要素並びの##前処理字句(実引数の中の##前処理字句を除く)を削除し,その直前にある前処理字句をその直後の前処理字句と連結する。プレースマーカ前処理字句は特別に扱う。すなわち,二つのプレースマーカ前処理字句を連結すると,一つのプレースマーカ前処理字句となる。プレースマーカ前処理字句と、プレースマーカでない前処理字句を連結すると,そのプレースマーカでない前処理字句となる。その結果が正しい前処理字句にならない場合,その動作は未定義とする。

(*゚ー゚) ……
読みにくいので、本質ではない部分を中略する。

置換要素並びの##前処理字句(実引数の中の##前処理字句を除く)を削除し,その直前にある前処理字句をその直後の前処理字句と連結する。
(中略)
その結果が正しい前処理字句にならない場合,その動作は未定義とする。

なお、ISO/IEC 9899では中略部分はピリオドが使われない程度にまとまった文になっている。
こんな「プレースマーカの扱いについて書かれていると思ったらいつの間にか終わっている」文章にはなっていない。

さて、前処理字句というのは

ヘッダ名
識別子
前処理数
文字定数
文字列リテラル
区切り子
上の分類のいずれにも当てはまらない非空白類文字

のことを言う。
CALL_ は「識別子」なので前処理字句
( は「区切り子」なので前処理字句
その二つを繋げると CALL_( になるが、これは前処理字句ではない
ゆえに CALL_ ## ( の結果は未定義となる。

これに関しては、実はMSDNでも以下の様に「だめだよ」と明記されている。

The resulting token must be a valid token.

にも関わらず連結できてしまうのは、この「token」の定義がCの「preprocessing token」と異なるか、あるいは単にプリコンパイラがエラーにするのをサボっているのだろう。

スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/181-9e5d0d8a
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。