情報処理技術者試験テクニカルエンジニア(ネットワーク)合格した!!
プロプロセッサのマクロ展開と ## 演算子 (2)
前回を書いてからすっかり経ってしまったが、C/C++ プリプロセッサのマクロ展開と ##
演算子の話。
まずは、__TEXT
がどう解釈されるのかを見てみる:
#define __TEXT(quote) L ## quote
- 『16.3 Macro replacement』の「9」
-
A preprocessing directive of the form
# define identifier lparen identifier-listopt ) replacement-list new-line
defines a function-like macro with parameters, similar syntactically to a function call. The parameters are specified by the optional list of identifiers, whose scope extends from their declaration in the identifier list until the new-line character that terminates the#define
preprocessing directive. Each subsequent instance of the function-like macro name followed by a(
as the next preprocessing token introduces the sequence of preprocessing tokens that is replaced by the replacement list in the definition (an invocation of the macro). The replaced sequence of preprocessing tokens is terminated by the matching)
preprocessing token, skipping intervening matched pairs of left and right parenthesis preprocessing tokens. Within the sequence of preprocessing tokens making up an invocation of a function-like macro, new-line is considered a normal white-space character.
- 展開前
-
__TEXT(HELLO_WORLD)
- 展開後
-
L ## quote
- 『16.3.1 Argument substitution』
-
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a
#
or##
preprocessing token or followed by a##
preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the translation unit; no other preprocessing tokens are available.
quote
が HELLO_WORLD
に置換されるはずが、「preceded by a ##
」なので quote
は quote
のままにされる。で、その次はこれ:
- 『16.3.3 The ## operator』の「2」
-
If, in the replacement list, a parameter is immediately preceded or followed by a
##
preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence.
quote
が HELLO_WORLD
に置換される:- 置換前
-
L ## quote
- 置換後
-
L ## HELLO_WORLD
##
」だから quote
は HELLO_WORLD
に置換されないって言ったくせに、結局 HELLO_WORLD
になっちゃったよ?? いやいや、同じではない。さっきは「replaced by the corresponding argument after all macros contained therein have been expanded」だったが、今度は「replaced by the corresponding argument’s preprocessing token sequence」としか書かれていない。そう、ここでは、マクロ HELLO_WORLD
はマクロ展開を受けずにプリプロセッサトークンのままになるのだ。そして、最後にこれがくる:
- 『16.3.3 The ## operator』の「3」
-
For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a
##
preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token. If the result is not a valid preprocessing token, the behavior is undefined. The resulting token is available for further macro replacement. The order of evaluation of##
operators is unspecified.
##
の処理前-
L ## HELLO_WORLD
##
の処理後-
LHELLO_WORLD
LHELLO_WORL
こそが、このエラーメッセージの元凶だ:
(11) : error C2065: 'LHELLO_WORLD' : 定義されていない識別子です。
では、最後に復習を。ちゃんとコンパイルされる方のマクロ呼び出しである TEXT(HELLO_WORLD)
は、こんな感じで展開されていく:
-
TEXT(HELLO_WORLD)
-
__TEXT(quote)
-
__TEXT(HELLO_WORLD)
-
__TEXT("Hello, world.")
-
L ## quote
-
L ## "Hello, world."
-
L"Hello, world."
プロプロセッサのマクロ展開と ## 演算子 (1)
Windows API には TCHAR
型というものがある((MSDN ライブラリでは TCHAR
は unsigned char
の typedef
だと解説されているが、Visual C++ 2008 Express Edition に付いてきた WinNT.h では char
の typedef
になっていた。)):
#ifdef UNICODE typedef wchar_t TCHAR; #else typedef char TCHAR; #endif
wchar_t
型ではないのでワイド文字としては扱えず、char
でもないので標準 C ライブラリなどの各種 API にも渡せないという扱いづらいやつだが、コードページから Unicode への過渡期には大きな役割を果たしてくれた型である。
さて、この TCHAR
型には TEXT()
マクロという相方がいて、“TCHAR
リテラル”とでも呼ぶべきものを書けるようになっている。すなわち、TEXT("some string")
と書くと、マクロ UNICODE
が define
されているかに応じて以下のいずれかに展開される:
UNICODE
が定義されているL"some string"
UNICODE
が定義されていない"some string"
define
されている。必要なところだけ抜き出してきて整形してある。)):
#ifdef UNICODE # define __TEXT(quote) L ## quote #else # define __TEXT(quote) quote #endif #define TEXT(quote) __TEXT(quote)
キーになるのは、##
という演算子だ。これは、マクロの置換テキストの中でのみ利用可能な演算子で、マクロの実引数を字句レベルで連結するという離れ業をやってのける。これにより、TEXT("some string")
は、__TEXT("some string")
から L ## "some string"
を経て L"some string"
に展開される。
ところで、ここに1つの疑問が湧く。なんで、__TEXT()
マクロを経由しないといけないのだろう? シンプルに「#define TEXT(quote) L ## quote」ではダメなのだろうか?
結論から言うと、このシンプルな解はダメだ。以下の2つをそれぞれコンパイルしてみれば分かる:
#include <windows.h> #include <tchar.h> int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) { MessageBox(NULL, __TEXT("Hello, world."), TEXT("hello"), MB_OK); return 0; }
#include <windows.h> #include <tchar.h> #define HELLO_WORLD "Hello, world." int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) { MessageBox(NULL, __TEXT(HELLO_WORLD), TEXT("hello"), MB_OK); return 0; }
__TEXT()
マクロに渡す "Hello, world." という文字列を直接書くか HELLO_WORLD
というマクロ経由で渡すかということだけだ。ただそれだけの違いで、コンパイルができたりできなかったりする。そして、__TEXT()
ではなく TEXT()
を使えば、HELLO_WORLD
マクロ経由でもコンパイルできるようになるのだ。では、これを切り口にしてプリプロセッサのマクロ展開と
##
演算子の面白い挙動を見ていこう、……と思ったが、長くなってしまったので今日はこの辺りまでで。ちなみに、全てを物語るのは、コンパイラからのこのメッセージだ:
(11) : error C2065: 'LHELLO_WORLD' : 定義されていない識別子です。
ビープを鳴らさない
こんなに簡単な設定だったとは驚き。なんかレジストリとかゴチャゴチャいじらなきゃいけないのかと思ってたよ。
C:\ の直下にはインストールしない方がいいと思う
最近はあまり見ないが、インストール先を C:\ の直下にしようとするインストーラやインストールインストラクションがある。
C:\ の直下がゴチャゴチャするのが何となく嫌なので、この手のソフトウェアは C:\opt というディレクトリの下にインストールしている。そう、今まではただ「何となく」こうしていたのだが、先日、「実はこうしないとヤバいんじゃないの?」と思うところがあったので、日記にでも書いてみることにした。
結論から言うと、こういうことだ:
- C:\ の直下にインストールしちゃうと、そこのバイナリとか誰でも書き換えられちゃいませんか?
Windows のパーミッション設定をよく理解できていないのかもしれないが、つい先日 32ビット版の Windows Vista Business をクリーンインストールしたばかりのこのマシン、Authenticated users グループに C:\ の直下への書き込み権*1がある。そんな設定をわざわざやった覚えはないので、多分デフォルトなんだろう。
で、Windows では、ディレクトリの中にファイルやサブディレクトリを作ると基本的には親ディレクトリの ACL が引き継がれる。だから、C:\foobar みたいなところにアプリケーションをインストールしてしまうと C:\foobar には要するに誰でもが書き込めるようになってしまう。C:\foobar\foobar.exe の内容だって変更し放題だ!!
これはもう、トロイの木馬を仕込んでくれと言っているようなものじゃないのか!? 他にも、C:\foobar\bin を PATH
に加えてたりしたら「DLL インジェクション、歓迎です」ってことじゃないのか!?
C:\Program Files 以下では、もちろんそんなことはない。管理者以外には、書き込み権なんて当然無い。これこそ、誰でも書き換えられる C:\ の直下にはアプリケーションなんてインストールしてはダメってことの現れなんじゃないだろうか。
もちろん、C:\opt を作るだけではダメで、ちゃんとしたパーミッションの設定をしないといけない。ポチポチ設定するのもいいが、お手軽なのは C:\Program Files の ACL をコピーすること。これには、いったん C:\Program Files\opt を作ってからこの opt を C:\ の直下に移動すればいい。ただし、C:\Program Files の下にあるときに opt の ACL 設定で「このオブジェクトの親からの継承可能なアクセス許可を含める」を外して ACL をコピーしておくのを忘れてはならない。
「そこまでするんなら C:\Program Files にインストールすればいいのに」と思われるかもしれないが、C:\ の直下へのインストールを案内しているソフトウェアには「インストール先のパス中に空白があるとダメ」というのがあったりするのである*2。
Consolas で M+2VM+IPAG circle で 9pt です
コーディングするときのフォントは重要だね。
結局は好みの問題なのでどれが一番なんてのはないと思うが、「すごくいいな、これ」と思うのがあったのでメモ。
http://cl.pocari.org/2007-04-06-1.html
Consolas はものすごくいいんだけど日本語がアレだな、と思っていたところに最高の組み合わせ。M+2VM+IPAG circle は濁点と半濁点の区別がつきやすいのが非常によい。
8pt から 9pt ぐらいがいい感じ。10pt だとやや大きいように感じるのと、Eclipse で 10pt にすると日本語部分だけ異様に小さくなってしまう。なんでかは分からない。
でも、Quest PuTTY*1 で 9pt にするとなぜか縦に潰れたようなメトリックスになるので、これだけは 10pt で。
環境変数 DYLD_LIBRARY_PATH で Xcode の Subversion を差し替える
いろいろと訳((HTTP (HTTPS でなく) 上で SPNEGO 認証がしたい、svn help
で日本語が出て欲しい、とか。))有って、ちょっとカスタマイズした Subversion を使っている。なかなかいい感じに使えていて自画自賛の心境だったが、svn
コマンドだけ*1じゃダメだ。やはり、Xcode から使えないといけない。
「たしか Xcode って svn
コマンド蹴ってるだけだったからチョロいよなー。」と甘く見ていたら……、違う!! ちゃんと libsvn_* の API を使っていらっしゃる((「otool -L /Developer/Library/Xcode/Plug-ins/XcodeSubversionPlugin.xcplugin/Contents/MacOS/XcodeSubversionPlugin
」とかすると一目瞭然。))。
だが、その程度で慌ててはいけない。多少カスタマイズしただけだし、バイナリ互換なはずだ……、多分。「なら、LD_LIBRARY_PATH
でいけるだろう」と思ったが、よく考えてみたら OS X は ldd
じゃなくて dyld
だった。でも、世の中はよくできている。DYLD_LIBRARY_PATH
とかいう同じような環境変数がちゃんとあった((詳細は dyld(1)
にて。))。
最後の問題は、この環境変数をどうやって Xcode のプロセスに設定するか。Windows や Linux*2 みたいな設定箇所を探して「システム環境設定」をウロウロしたがどうにも見つからない。最悪、ターミナルから
$ DYLD_LIBRARY_PATH=/opt/subversion/lib open /Developer/Applications/Xcode.app
でもいいが……、本当に最悪だ。
紆余曲折の末、http://developer.apple.com/qa/qa2001/qa1067.html に到達。OS X のことが少し嫌いになる。「環境変数なんていう泥臭いものをエンドユーザに見せないのがスマート、ってのも分からなくはないが……」と内心ボヤきつつ仕方なしにコピペしようとするに……、これ画像かよっ!! Apple のことも少し嫌いになる。写経はゴメンなので、適当に find
してきた *.plist
をテンプレとしてコピー。plist
要素の version
属性が 1.0
になっていて、意味もなく得した気分に浸る。
勝利を確信して、ターミナルから env
を実行……したが、設定したはずの DYLD_LIBRARY_PATH
が見あたらない。一緒に設定した動作確認用の環境変数はちゃんと出てきていたので、「bash
がクリアしてるのか?」と ps -xE
してみる。案の定、launchd
*3 とその子プロセス達には DYLD_LIBRARY_PATH
がちゃんと設定されていて一安心。
満を持して Xcode から Subversion リポジトリにアクセスすれば、全てが思い通りに動いて非常に満ち足りた気持ちになる。
OS X が UNIX で本当に良かったよ*4。