せばよい (offsetof は size t 型の値を返す ) 。 #include く stddef. h > struct example { Char unsigned long Size t int fname S lZe status ; [ 64 ] ; i=offsetof(struct example, status) ・ 型をパラメータとして渡すことから , off setof は通常の関数として実装することはて、 きない。大多数の処理系て、は List 1 のような 定義のマクロになっているものと思われる。 ただし , なかにはどうしても 0 をキャスト して望みの構造体へのポインタと見なせる 値を作り出すことは難しいという処理系も あり , そのような場合にはコンパイラが組 み込み機能として処理するしかないだろう と ANSI の Rationale( 理由書 ) は述べてい 演算子ツ″とー > ″の優先順位 &x. fname [ 0 ] は & (). fname [ 0 ] ) とい う意味て、ある。ヾ . 〃や添字づけ ] 〃 ( 添字づ けも一種の演算子と考えることがて、きる ) な どは , 単項の、、 & クよりも結合力が強く , 優先 される。そのため , この場合にはカッコは 不要て、ある。、や、、 [ ] 〃は単項演算子よりも まだ高い優先順位を持っていて , 実のとこ ろもっとも優先順位の高い演算子て、ある。 それて、は , x. fname [ 0 ] において、、 . 〃と、、 [ ] 〃 はどちらが優先されているのだろうか。 Fig. 1 postfix-expression, p 「 imary-expression の構文定義 postfix—expression . prmary—expresslon postfix¯expression [ expression ] 十十 ー > identifier ( argument-expression 0 が ) Table 1 ( expression ) string-literal constant identifier pnmary¯expresslon . postfix—exression postfix—exressi 0 n postfix—ex 「 essiO n postfix—exresslon . identifier postfix¯exression プレ形式 ボスト形式 ( 十十 p)—>m p 十十—>m ポインタへの加減算 メンバへの加減算 p—>m 十十 十十 p—>m more p 十十一 > m = q ー った 作は ( 少なくとも公式には ) 許されていなか 許されるのて、ある。 K & R の時代にはこの操 ひとって、あるとすると , 次のような表現が ポインタて、あり , m がその構造体のメンバの n になることて、ある。 p および q が構造体への ーを適用した式もまた postfix-expressio こて、注目すべきことは , 後置の十十や の名の由来だと思われる ) 。 ら右」へと結合するのて、ある ( それが postfix 後置式のカテゴリに登場する演算子は「左か が優先される。あるいは表現を変えれば , 許される。ということて、 , 結論としては、、 . ression なのて、 , その後に添字をつけるのは dentifier という式全体は , また postfix-exp になっている。ただし postfix-expression. i 含まれるメンバ名て、なければならない されない。実際には左辺が表す構造体型に よび、、一 >〃の後には idetifier( 識別子 ) しか許 この構文定義から明らかなように ことがわかる。 子力℃て、もっとも結合力の強い演算子て、ある ないため , これら postfix-expression の演算 3.1 ) には , カッコを除けば演算子は登場し とがわかる。 primay-expression (ANSI 3. および後置のデクリメント、、 ー〃て、あるこ 選択、、一 > 〃 , 後置のインクリメント、、十十〃 , 呼び出し、、 ( ) 〃 , メンバ選択、、 . ク , 間接メンバ の類としては , 配列の添字づけ、、 [ ] 〃 , 関数 postfix-expression に含まれている演算子 3.3.2 , Fig. 1 ) 。 という構文カテゴリに属している (ANSI れらはどちらも postfix-expression ( 後置式 ) ー > 〃というのは , 実際に ANSI C ー more 117 のを切り出すように構成されているからて、 部は , トークンとして解釈可能な最長のも が保証されている。コンパイラの字句解析 に分解されて期待どおりに解釈されること 〃とー > 〃というふたつのトークン は避けるべき表記て、はあるが , 一応文法上
特集℃プロクラミ : タの秘 ・ニ項演算子の両側には , 原則として空白 をひとつずっ置く。ただしとくに理由の ある場合にはそのかぎりではない。 △ sum=a 十 b; 〇 sum 凵 = 凵 a 凵十 ub 二項演算子は , 単項演算子よりも優先順 EOF) { . X if 凵 (c 位が低いのて、 , とくに単項演算子と混じっ / * { の直前の空白がない * / た場合には , このように書くと読みやすく プログフミングスタイルー EOF) 凵 { . なるからだと思います。 ■関数名やマクロの後ろにつづく、、 ( ' ' の前に points 凵 = 凵 a 十十凵十凵ー b は空白を置かない。 たとえば , 以下に , K&R スタイルを中心に , プログ X printf 凵 ("hello world*n") ・ ラミングスタイルを項目に分わけてまとめ points 凵 = 凵 a —b printf("hello world*n") ; は , まだいいのて、すが , ておきます。なお , 各項目中の〇 x △は , 〇 K&R スタイルに合致しているかどうかを表 このように ・・・などと関数と if, for, points 凵ー ー凵 a ー 現するものて、す。つまり , 〇は K&R スタイ の書き方を区別しておくと , 区別が楽にな だとか , り , 工デイタやツールて、検索するとき作業 ルて、あるということを示しています。した points 凵ー ー凵 a¯ が楽になることがあります。 となると , わけがわからなくなります。 がって , x , △の書き方が間違っているとい ■、、 ( ′ ' と , その直後の文字との間には , 空白 二項演算子の両側に空白を置かない例は う意味て、はありません。また凵はスペースを を置かない。、、 ) ′ ' とその直前の文字との間に 散在しています。とくに , 意味的に , 演算 表しています。 の結果に重点を置いているような場合に , ■インデントの幅はスペース 4 個分にする。 も空白を置かない。 getchar( ) ) ! = EOF) { そのような例が多いようてす。次のような X if 凵 (c whil = EOFu) { . EOF) { . 書き方て、す。 〇 if (c 空白を置いてもよさそうなものて、すが , 凵ュ」一にユュ亠」ュ亠 pu tch a r ( ' sum 凵 = 凵 a 十 b ■構造体のメンバをアクセスするためのピ あまり空白ばかりだとかえって見にくくな リオド : ″や“ー > 〃の両側には空白を置かな るということて、しようか。少なくとも , カ ッコがつづくときに , 次のように書くと , [ュュュ亠」ー」ー」 putchar (c) ・ 結構間延びしてしまいます。 X cursor 凵・凵 x = newptr 凵ー > 凵 h getchar( 凵 ) 凵 〇 cursor. X インデント幅については , 多くの流儀が while ( 凵 ( 凵 c newptr— > h ー > クに関しては , 優先順位が高い ・カンマ ) ″ , セミコロンヾ , ″の後には , 原 ありますが , 大部分はスペース 2 ~ 8 個てす。 K & R 第 1 版て、はスペース 5 個て、した。スペー 則として , 空白をひとつ置く。 ため , 空白を置かないほうが , かえってつな がりを明確に表現て、きるためと思われます。 x printf ()i = %d*n", i) ; ス 2 個にする人も多いようて、す。とくに GN ・三項演算子ツ : 〃の、ツ←〃の両側に printf ()i = %d*n", 凵 i) ; U のソースに目立っときがあります。 BorIa 〇 これは C の書き方というより , 英文を書く は , 空白を置く。 nd C 十十のリファレンスマニュアルのサン 場合には普通に行われていることて、す。た プルプログラムはスペース 3 個になっていま X abs だし , 英文タイプの場合 , セミコロンの後 (a > = 0 ) 凵 ? 凵 a 凵 : 凵ー a , 〇 す。 abs これも優先順位の関係だと思えば納得て には , スペースを 2 個タイプするのが普通の TAB 幅を指定て、きるエデイタてあれば , きるてしよう。 「 TAB = 4 」に設定して編集するとよいてしよ ようて、す。 ー単項演算子とそれを作用させる式などの ・キャスト演算子に用いるカッコ〃の直後 う。ただし , ソースリストを公開する場合 間には空白を空けない。 には空白を置く。 は注意が必要てす。 ■予約語 if , for, while, switch などの後ろ (int)c , X i 凵十十 〇 に必要な′の前には空白を置く。感 ) ″の後 (int)uc ; 〇 i 十十 ; に感 { ″を書く場合には , 間に空白を置く。 単項演算子の優先順位はかなり高いのて , ■ return の後に不必要な ( ) ″はつけない。 = EOF) 凵 { . 強いつながりを持っているという意味を含 X return ( の・ / * if の直後の空白がない * / ませて , つないて書くのだと思います。 〇 return 0 , fo 「文の本体が空文の場合 List for (express ionl; expression2; expression3) 1 特集 C プログラミングの秘訣 61
なプログラマにとっても欠かせない カッコの必要性を減らすために , 連結記 p 三 q という形の式は , 実質同値式 (mater 思いになるて、しよう。 号の優先順が定義されています。否定は合 ial biconditional, 実質双条件式 ) と呼ばれ , 私はプログラミングの入門コースを教え 接に優先し , そして合接は離接に優先しま (pDq)A(qDp) とも書けます。これらの新 ている教師て、すが , 非常に優秀な生徒も含 す。そこて、 , カッコなして、書かれた式が最 たな連結記号は , 不可欠てはありません。 めて , とても多くの生徒が , ごく簡単な論 初に解釈されます。 真理値表から分かるように , PDq は .pvq 理式の操作に手間取っている様子を見て , 真理関数の式には , 他にも二つの重要な と置き換えることがて、き , p 三 q は pqv .(p 不思議な気持ちになることがしょっちゅう 連結記号が使われます。それらの記号は , q) と置き換えられます。三つの基本演算子 あります。て、も , 複雑な式になると , 経験 つ ( →とも書く ) と三て、す。これらには通常 , (not, and, or ) て、すら , 三つとも全部は必 豊富なプログラマて、さえ , ドジを犯すこと △と同じ優先順が与えられ , その真理値表は 要ありません。 not, and と or のどちらかだ がありますが。 Table 2 のとおりて、す。 pnq という式て、は , けて、 , あらゆる式を表現て、きることが証明 p が前件 (antecedent) , q 力す後件 (consequen されています。たったーっの演算子て、 , ど 真理関数式 t) と呼ばれます。こういう式全体のことを , んな式て、も表現て、きる , という演算子を定 実質条件式 (material conditional) と呼び 義することすら可能て、す ( シェファーの筆法 本稿は , 真理関数論理の基本概念への入 ます。 〔 NAND 〕など ) 。 門編て、す。付随して提供するプログラム QU 実質的 , という形容詞がなぜ式の名前に 冗長な連結記号は , 実用的に便利だから INE を使うと , 教科書に載っている , 紙と鉛 使われているのか , 不思議に思われるかも 使われているのて、あり , 複雑な式の扱いを 筆を使って解く無味乾燥な練習間題よりは , しれません。この真理関数式と , 日常言語 大幅に単純化してくれます。 面白くそして効率的に この主題を探究て、 の if... then 文は , 明らかに関係はあります 真理関数式の場合は , その各項に何らか きるて、しよう。 が , しかし区別して考える必要があります。 の真理値を代入したときの式の値は , 真理 論理式 (logical expression) 〃という言葉 その区別をしないと , 不合理が生じます。 値表の規則を当てはめれば分かります。し は , 真理関数論理 (truth-functional logic) 「 if ロンドンがフランスの首都 , then 2 十 2 = かしプログラマは論理式に対して , プログ の分野て、は使われません。代わりに , 式 ( fo 4 」という文と , 「 if ロンドンがフランスの首 ラムの実行を手作業て、辿りながら , 真・偽 rmula(s)) という言葉を使います。式は論理 都 , then 2 十 2 = 5 」という文は , 実質条件式 の結果を判定しようとします。て、も , われ 式と似ていますが , しかしその項は , 不特 のインスタンスとして解釈したときには , われプログラマも , もっと一般的な問題を 定の命題を表す一つの文字て、す ( 代数と同じ どちらも真て、す。しかし日常言語の条件文 考えてもよいのて、はないて、しようか。真理 て、 , 文字は不特定の値を表します ) 。文字 としては , どちらもナンセンスて、す。 関数式は , その項の真理値のあらゆる可能 は , ありうる値が真と偽の二つて、あるとこ 実質条件式のこの定義は , 論理の展開に な組み合わせに対して , いかなる値をとる ろの , 変数を表します。それらを , 特別な とって便利て、あることが証明されています。 か ? 記号を使う連結記号 ( 論理式の演算子に相当 ) 日常言語の文と , 真理関数式との関係は , 三つの可能性が存在します。 て、もって結び付けます。 難解て、複雑な哲学的問題て、あるため , ・その式はその項にどんな真理値を代入し 否定 ( not ) は , 横線や + て、表します。単一 て、は論じません。良い参考文献として , W. ても真に評価される ( その単純な例が pV の変数に対して使うときは , 文字の上に V. O. Quine の Method of Logic [ 1 ] や , P. p や PDP)0 このような式は , 妥当て、ある の記号を書きます〔文字が横線という、、帽子〃 F. Strawson の lntroduction to Logic The (valid) とか , 同語反復 (tautology, トー をかぶったような形になる。項の前にと ory ②があります。 トロジー ) て、ある , と呼ばれます ( tautolo 書く書き方もある〕。合接 ( 論理積 ) を表す記 Table 2 真理関数の連結記号 号は△て、すが , しかし通常それは , 代数て、積 を表現するときのように項を並べて書いて 表します。最後に離接 ( 論理和 ) て、すが , れは V て、表します。曖昧さを防ぐためには , カッコを使います。カッコがないとたとえ ば , pVqAr は pv (qAr) とも (pvq) Ar とも解 釈されます。 とは 真真偽偽 真偽真偽 真真偽偽 PDq 真 真 真 真偽真偽 P q 三真偽偽真 真理関数論理のためのスクラッチバッド 23
ile のカッコの中は , 次のように読めるて、し 「まず , c に getchar でもらった値を代入し てだな , それが EOF でなければループの中 の処理をする」 秀逸なのは , 「それが」のところて、す。 C 言 語においては式も値を持っていることを利 用した芸当て、す。て、すから , 厳密には , c と EOF を比較しているというのは正しくない のて、すが , あえてそのように思い込んて、い るとしても , この例て、はあまり差し支えは ないて、しよう。 て、は , これをカッコを外して書くには , どうすればいいて、しようか。 getchar( ) ! = EOF) { while (c これは無茶て、す。代入演算子の優先順位 より , 比較の演算子のほうが高いのて、 , ge tchar の値は先に EOF との比較に使われてし まい , 比較して同じだったかどうかという 結果の値 , すなわち 0 か 1 が c に代入されてし まいます。 while (c = getchar( ) , c ! = EOF) { 処理としては間違ってはいません。しか し , 問題はふたつあります。まず , while を 見てから , その条件の判断部分に到達する まてに , c getchar ( ) という余計な処理 がひとつ入ったことて、す。 while からこれが 遠くなればなるほど , いま何の条件を考え ていたのか忘れてしまうてしよう。もっと おもしろくないのは , せつかく c getch ar( ) という式が値を持っているのに , それ をまったく利用していないことて、す。 て、は , while のループの中に , 処理を入れ てしまうというのはどうてしようか ? break ; EOF) C getchar( ) ; while ( 1 ) { 今度は , 確かにカッコの数は減りました。 しかし , これて、は単なる無限ループを作っ て , 中から強制的に飛び出すことになって しまいますから , 今度は while を使う必然性 が弱まってしまいます。 結局 , この場合においては , 最初の書き 方に比べてあまりうまい方法はなさそうて、 す。というより , 最初の書き方がうますぎ る , といってもよいてしよう。実際 , この 書き方は , C 使いになるには , 必ずマスター しておかねばならない表現のひとつだとい っていいてしよう。 そうすると , 今度は , List 4 のような書き 方に対して , むしろ違和感がなくなり , 理 解しやすいといった現象も現れるのて、はな いて、しようか。 getchar の記法は , まさに w hile の書き方と同種だからてす。 思の順序を妨げない、 さて , あなたがもし相当腕利きの C 使いな ら , 何を当たり前のことをいうのだと思っ ているかもしれません。まだまだ甘いな , と思っている人もいるかもしれません。ま た , 初心者の方なら , いったいどう書けば 奥義をこっそり書いておきま 思います。そこて , どっちがいいんだ , と と多少イライラしてきた頃だと 特集 C プログラミンの秘 みれば , 思考が妨げられないという意味が , なんとなくわかるのて、はないて、しようか。 fopen (filename, "r") ・ ! = NULL) { if の判断の中に fopen を入れるよりは , 思 考の順序の点て、は明らかに勝っています。 if (fp なせ左辺に 定数を書がないが ~ = よく迷う , ある判断に決着をつけることが 前述の奧義を指針とすれば , もうひとつ て、きます。 while (EOF ! = = getchar( ) ) ) { いいのだ , 迷った方へ す。 この奥義を基本に , 先ほどの例を考えて もちろんそちらを優先すべきてしよう。 俺のほうがいいぞという自信があるなら , もし , 自分なりの意見をすてに持っていて , とはいえ , あくまてこれは流儀てすから , ことになるのてす。 かを , 体験的に身につけて , 初めて理解し せん。どう書けば思考の順序を妨げないの わかったつもりてもまったく意味がありま のようなものてすから , 読んて、頭て考えて たったこれだけのことてす。これは秘伝 ムを書こう 思考の順序を妨げないようにプログラ このような書き方を見た経験のある方は 多いと思います ofopen の例だと List 5 のよ の左側に持ってくることて、す。 特徴は , 定数を二項演算子 , ここては当 = ク うになります。 なぜこのよ うにするかというと , 次の例を見てくださ if (i if (i もしれません。 し , うつかりして , 次のように間違えるか 実行するというごく単純なものてす。しか この処理は , i が一 1 の場合にカッコの中を 特集 C プログラミングの秘訣 49 あります。この種のバグは , 気がつかない というあたりて初めてバグに気づくことも とてす。実行してみて , 何か動作が変だ , ため , コンパイルが正常終了してしまうこ もかかわらず , いずれも文法的には正しい の特徴は , 処理の内容はまったく異なるに いをした経験があるはずてす。この間違い い」というャッて , 誰ても必ずこの種の間違 これが C 言語て有名な , 「 = = と = の間違
もっとも小さくするオプティマイズ , ー 02 オ かように TempIate がサポートされたこと プションにより , スヒ。ードをもっとも速く 語仕様として型をパラメータに するオプティマイズが可能て、す。 持っクラスの定義が可能となりました。か 実際にどの程度のオプティマイズが可能 くして , generic. h 中のあの複雑怪奇なマク かについては , List 3 , List 4 を参照してく 口から晴れて解放されます。ともに喜びを ださい。なんと , 関数の中身すべてが消え 分かちあいましよう ( 笑 ) 。 こうなると , もはやひた ところて、 , Template の機構は , 純粋なオ てしまいました。 プジェクト指向からは邪道だ ! と非難さ すら笑うしかありません ( 笑 ) 。 式の結果を , printf て、表示して中身の削除 れるかもしれません。しかし , それをいう なら C 十十は元々純粋なオプジェクト指向言 を防いだのが , List 5 て、す。 まだムダが残っており , 完璧とはいえま 語て、はないじゃないて、すか。「いいとこどり」 せんが , レジスタが多用されかなり高速て、 て、進化してきた節操のない言語なのて、す。 我々も節操なくばんばん使いましよう。 インクリメント・テクリメント演算子の例 Template の実際を , List 1 , Fig. 3 に示 しておきます。ご覧になればわかるように クラスだけて、なく , 通常の関数定義にも Te mplate は有効て、す。 Fig. 3 List 1 の実行結果 stk. pop( ) = 9 stk. pop ( ) = 8 stk. pop ( ) = 7 stk. pop ( ) = 6 stk. pop( ) : 5 stk. pop( ) : 4 stk. pop( ) : 3 stk. pop( ) : 2 stk. pop( ) = 1 stk. pop( ) = 0 a=2 b=l List 1 : / / プレインクリメント・ポストインクリメント 2 : #include く iostream. h 〉 3 : 4 : class F00 { 5 : int ; 6 : public: Foo(int (n) : n(nn) { } 7 : / / プレインクリメント int operator + + () ( return + + n ; } 8 : ・ } / / ポストインクリメント int operator + + (int) { return n + + , 9 : 〃この場合引数は意味を持たない 10 : int operator=(int (n) { return れ 11 : operator int() { return n; } 12 : 13 : } : 14 : 15 : int main() F00 f00 = 0 ; 17 : cout くく” + + f00 = ”くく + + f00 くく endl ; 20 : f00 = 0 : cout くく” f00 + + = ”くく f00 + + ; 22 : return 0 ; 23 : ) ・ インクリメント・テクリメント演算子 蛇足て、すが , AT&T C 十十 2.1 準拠のひ とっとして , インクリメント・デクリメン ト演算子の前置・後置が区別て、きるように なりました (List 2 参照 ) 。 オプティマイズ BC 十十 3.0 て、は , 我々の長年の夢て、あった オプティマイズがついに , ついにサポート されました。 Ver. 1 . 5 暫定版からのユーザ て、ある筆者は , 思わず感涙にむせび泣いて しまいます : ー ) 。 これは , 今まて、の「スヒ。ード」「サイズ」な どというあるのかないのかわからないよう なセコいコード生成指定とはわけが違いま す。いきなり本格的なオプティマイズなの て、す。オプティマイズ関係のオプションか らして , 20 個にとどかんとする勢いて、す (T able 1 参照 ) 。 オプティマイズ項目はかなり細かく分か れていますが , ユーザの簡便を図り , スヒ。 ード , サイズのそれぞれに , 最大限の最適 化を指示するためのオプションが用意され ています。ー OI オプションにより , サイズを オプティマイズ例 ( サンプルプログラム ) List 1 : / / もとのソース 2 : void exprl() int a = 1 , b = 2 , c = 3 ; 4 : 5 : 6 : a = a 十 b 十 c; b = a > > 1 : 7 : a = b % 10 : 8 : List 3 のコンバイル結果 ( 1 ) List 1 : / / コンパイルオプション一 02 でコンパイルしたアセンブラ結果 2 : @exprl$qv proc near 3 : ret 4 : @exprl$qv endp 5 : 〃なにもありませんが、この無意味な関数の中身がすべて消えてしまった 36 C MAGAZINE 1992 4
A かっ a B かっ a A かっ b B かっ b の 4 通りが考えられます。「 B かっ b 」のみは , 前記て、説明した方法て、対処て、きますが , 残 り三つはそれぞれ特別に処理しなければな りません。とてもやっていられません。 の場合は前述した安直アルゴリズムて、コヒ。 ーします。幅が狭いのて、 , こうしてもそれ ほど遅くならないて、しよう。 また , C にはビットシフト演算子 ( 、、 > > 〃 や、、くくつはあるのて、すが , ローテートシ 配布ファイル ったく不要て、す。 以上て、使った手法は , IBM-PC ならばま リストを参考にしてください プラを使っています。 フト演算子はないのて、 , このあたりはソース インラインアセン 86 C MAGAZINE 1 2 4 能て、す。その際の注意を少し述べてみたい r ) を用いて djgcc 用関数を作成することが可 をした経験があれば , gas(GNU AssembIe MASM などて、アセンプラブログラミング 386 版 gas プログラミングについて としてください gcc ー 0 test test. cc -lgr -lpc test. cc を再コンノヾイルするには , ッドに日本語が使えることに注目してくだ ることは確認て、きると思います。 Text メソ ある動作はしませんが , 各関数が動いてい びに画面表示が変わります。あまり意味の に作ってみました。リターンを入力するた リジナルの djgcc からの移植てはなく , 独自 今回のサンプルプログラム test. exe は , オ サンプルプログラム してください gr. a を djgcc の lib サプディレクトリにコピー 付録ディスクの libgr98. lzh を展開し , lib 号の付録ディスクて、配布します。 ラフィックライプラリの全ファイルを今月 前回の C 版のライプラリも含めて , 98 版グ と思います。 文法の違い MOVL %EAX, %EBX MOVW %AX, %BX MOVB %AL, %BL る を区別するために命令に・ b / w / ドをつけ ・バイト命令 , ワード命令 , ロング命令 ・レジスタには・ % ' をつける ときは cpp を通すことになる ) ・ gas 自体はマクロが使えない ( 使いたい ・メモリ参照書式の違い ( 後述 ) ほかには , ラーになりません。厄介て、す。 代入 / 論理演算 / 加減算などはアセンプルエ ジスタ同士もしくはレジスタ・メモリ間の わらなかったりするのて、よいのて、すが , レ ラーとなって誤りに気がつくか , 意味が変 chg) などて、は , 逆に書いてもアセンプルエ ト入出力 (in, out) , ビットシフト , 交換 (x オペランドがひとつ以下の命令や , ポー なのて、す。 という点て、す。つまり , MASM と順序が逆 ティネーションの順である ・オペランドの順序は , ソース , ディス を悩ませる gas の文法上の大きな特色は , MASM に慣れた人間にとってもっとも頭 Fig. 6 スタックの状態 MOVL $ 100 , %EAX ・即値には・ $ ' をつける INT $0x21 ・絶対番地オペランドには , を * ' をつける ・コメントは C 十十ライクに , / / ・・・ もしくは / * ・・・ * / とすればよいわけて、す ( line. s 参照 ) 。 MOVL 16(%ESP), %EAX 16 て、すから EAX に ARGI をロードしたければ , 4n 十 8 = するのは簡単て、す。たとえば n = 2 のとき , の状態を頭に入れておけば , 引き数を参照 て、のスタックの状態を Fig. 6 に示します。 の n 個のレジスタをブッシュ ( 退避 ) した状態 をコールし , f00 ( ) が入り口て、 AAA—ZZZ foo(int ARGIv int ARG2, int ARG3) ; ます。例として , は , 引き数を逆順にスタックへブッシュし djgcc て、は , 通常の関数をコールする前に スタックの状態 破壊しないように注意しましよう。 て、す。 Turbo C や MS-C の習慣て、 EBX を EAX, ECX, EDX ES, FS, GS 関数内て、値を変更してよいレジスタは , 破壊可能レジスタ などを頭に入れておく必要があります。 ないので注意 ントで記述できる。コメントとはなら ・セミコロン・ ; ' によりマルチステートメ ESP はこ を指す一一→ ARG3 ARG2 ARGI EIP AAA ZZZ 十 4n 十 16 十 4n 十 12 十 0 十 4n (AAA ZZZ : ブッシュされたレジスタ ) 十 4n 十 4 戻り番地 十 4n 十 8 引き数
LANGIJAGE 真理関数論理 た MN) スクラゝチノヾッド A Scratchpad 館 T 頑ト FunctionaILogic Gianfranco Boggio-Togna/ 岩谷宏訳 (COMPUT R LANGUAGE, Dec. 円 9 1) 雑な論名まどんなプログラマをも混乱させる。真理 関数のためのスクラッチバッドは最高にややこしい問題 で解明を支援する ALGORITHMS ーま新第 0 馬 もた OMP リ R IANGIJ, Å GE A [ GO 翩期 5 5 P ・ BY ・ 5 種 P 提携記事 MPLEX ーに Gender 釦 印圓 R 胤ー 理論を完成させています。 プログラマは , 論理式ないしプーリアン ばよいのて、す。 真理関数論理は , 論理の基礎的な部分て、 ーっの論理式は , それの項 ( 複数 ) の真理 式をよく使います。条件文を使わないて、 , あり , その他の部分はそれの上に構築され 値の真理関数て、す。 何か実用性のあるプログラムを書くことは , ほとんど不可能て、す。条件文て、は , if の後に プログラミング言語と , それの論理式に ます。 何らかの形の論理プログラミングに手を 論理式が続きます。 は , 30 年あまりの歴史があります。記号論 出してみたい人は , 真理関数論理からスタ その論理式は , ふつうはごく単純て、す。 理にはもっと長い歴史があり , その一分野 x=0 〔 Pascal 記法 , C て、は x = = 0 〕とか i< ートしなければなりません。 て、ある真理関数論理 ( ないし命題論理 ) は , しかし , 論理プログラミングに関心がな コンヒ。ュータやプログラミンク言語が登場 n といった単一の項が , プログラムの変数 くても , 真理関数論理の十分な理解がどん するよりもずっと前に , 真理関数に関する と , 他の変数ないし定数との関係を記述し ます。その関係が成り立てば , その式は真 TabIe 1 演算子のアクションは表で定義される になります。 オペランド ときには , 複数の条件の組み合わせをチ ェックしなければならない場合があります。 どのプログラミング言語も , 複雑な式を構 築するために not , and, or という三つの演 算子を提供しています。これらの演算子の アクションは , TabIe 1 に示すような表て、定 義て、き , オペランドの値の組み合わせと式 の値を対比させられます。 これらの表は , 項の形式や意味は何も表 現していません。唯一かんじんなのは , 項 が真か偽かということて、す。そこて、このよ うな表を , 真理値表と呼びます。 実際 , 論理式というものの特徴は , 真か 偽かのどちらか ( = 真理値 ) にしか評価され という点にあるのて、す。論理式を評 価するためには , 各項の真理値さえわかれ not オペランド オペランド land オペランド 2 真 オペランド 10 「オペランド 2 真 オペランド 2 真 真 オペランド 2 真 偽 真 オペランド 1 真 オペランド 1 真 真 22 C MAGAZINE 1992 4
特集℃プログラミンおの秘 知っています。ただ , なかなかそれを主張 する人がいないのは , ある人がわかりやす いと思ったとしても , はたしてほかの人に とっても同様にわかりやすいだろうか , と いう決め手に欠けることが多いからて、しょ 定暑える ルをオープンする処理の書き方に , List 1 の 非常にポピュラーな定石て、すが , ファイ ようなものがあります ( もちろん , はないと思います。誰も責める人もいない こう書いても何も悪く まあ , 定石だから , 定石とはそうてなければ意味がありません。 想 ? ) して , 次の行の解釈に移っています。 1 行を一瞬見ただけて、即座に処理を理解 ( 連 ませんが , C に慣れたプログラマなら , この まさか本当にこう読む人はいるわけがあり ら , { } の中の ... を処理してと・・・・・・」 て , 結果が NULL でなければ値が真だか では " r ”だから読み出しか , ほんでもっ 名と , オープンするときのモードはこ イルポインタだな , fopen の中はファイル en の値を代入してと , fp というのはファ の処理をするんだな , なになに , fp に fop 「ふむふむ , if か , もし ( ) の中が真なら { } ように読みます。 いかもしれません。たとえば , List 1 は次の ますが , 初心者なら何のことやらわからな 処理は一見それほど難解て、はないと思い ないという人は , C 使いの中にはいないて、し て、す。そして , 今も , この書き方て、わから もちろん , C が使える人の中に , という意味 て、わからないという人はいませんてした。 うな時代もあったのて、 , このような書き方 C を勉強する人は K & R を読むしかないよ ろごろ出てくるからだと思います。 か考えてみましたが , おそらく K&R(2) にご さて , この記法がポピュラーなのはなぜ と書くのて、はありません ) 。 は , 何か処理が書かれるのて、あり , 実際に てしよう。 私も責める気は毛頭ありません。ただし , 私なら次のように書きます。 fopen (filename, " 「 " ) ・ ! = NULL) { if (fp ロならこう書くのだがと書いたところ , 間 なぜか ? 実は某所て、ついうつかり , プ ば値が真だから , { } の中の ... を処理し 今代入した値だな , これが NULL でなけれ ら読み出しだな , 次は if か , fp というのは プンするときのモードがここでは " 「”だか けだ , fopen の中はと , ファイル名とオー 「さてと , fp に , fopen の値を代入するわ どのように読めるてしようか。 うに考えながら読んてみるとわかります。 これは , 後者のリストを , 前者と同じよ 「順序が逆転した処理は , 思考を妨げる」 そこて、次のような仮説を立ててみます。 はないか , ということに気がっきました。 が , どうも思考の順序に関係しているのて、 ないか考えてみました。その結果 , この味 せん。そこて , もう少し科学的に説明て、き 人もそう感じるかといえば定かてはありま しかも , 私がいい味だと感じてもほかの たと思います。 を作り始めてから , 少なくとも 5 年はかかっ くわかるようになるまて、に , C て、プログラム たいへんて、す。私自身がこの味をなんとな 味がいいというのを理解してもらうのは てすか。 から書きたくないっていっているてはない ら」というものてした。余計わからない ? だ 意した回答は , 「こっちのほうが味がいいか し「どうしてなんだ」という質問があれば用 念のため書いておきますと , 上の場合 , も のて、す。あまり書きたくはないのて、すが , かっていたら , 前もって回答を考えておく すと , 質間されそうなことがあらかじめわ パソコン通信て、の秘訣をひとつ紹介しま そうて、す ( 3 ) 。 髪入れずに「どうしてなんだ」と質問が出た なんだ , あまり変わらないて、はないか。そ のとおり。て、も , あまり変わらないなりに も変わったところを見逃してはなりません。 もっとも顕著な違いは , if が現れたときの後 処理て、す。先ほどの例て、は , まず if を見た時 点て , いったん if の処理中て、あることを , 頭 の中のスタック ( 4 ) に積まなければなりませ ん。最初の記法て、は , 「ふむふむ , if か , もし ( ) の中が真なら { } の処理をするんだな , なになに・ 「・・・・・・結果が NULL てなければ値が真だか ら , { } の中の ... を処理してと・・ の間に処理が割り込んて、いるため , if という 文字を見てから , 条件判断まて、の間が長く なっているのがひとつの特徴てす。そんな 無茶苦茶長いわけて、はないのだから , この 程度ならいいてはないか , と考える人もい るかもしれません。それも確かにもっとも てはあります。 もうひとつの特徴は , 前者のカッコの数 てす。一般に , カッコの重なりが深いと , わかりにくくなります。かといって , むや ということては みにカッコを外せばいい ありません。 List 2 のように書く人は , よほ どの初心者がうつかり間違える場合て、もな ければ滅多にいません。 C 言語ては , 演算子の優先順位がはっきり 階級化しています。まず優先度の高いもの から処理され , 左右の順序は , 優先度が同 じ場合にのみ結果的に意味を持ちます。し たがって ,List 2 のように書くと , 優先順位 の高い演算子てある当 = 〃がまず評価さ れ , その後に fp につづく代入演算子や = クが 処理されることになります。すなわち , Li st 3 のようにカッコをつけた場合と等しく解 釈されます。 これはおそらく期待した処理の流れとは 全然違うはずてす。 一般論としては , 優先順位について , 絶 対に死んても間違いないという自信がある というほどの場合てなければ , たとえ余計 てあっても , カッコをつけておいたほうが 意表を突かれることは少なくなります。 特集 C プログラミングの秘訣 47
構造体 ( 2 ) 引 mo 第 12 回 今回は構造体について , 前回紹介できな かった話題をトピックス的に取り上げて 述べておきたい。 きだあきら いい換えれば , 構造体自身のアドレス 構造体のメンバに関して はその先頭メンバのアドレスと常に一致す る。前回構造体の内側には「穴」が存在する かもしれないことを述べたが , 決して先頭 れまて、構造体の構成要素 , すなわちメ には穴はやってこない。これは ANSI て、保証 ンバに関しては , どのような型が許される のかについてとくに説明してこなかった。 されている (ANSI 3.5.2.1 ) 。 なお , ときとしてプログラミング上て構 実際には構造体のメンバはどのような型の オプジェクトて、あってもよい ( 単に「オプジ 造体の特定のメンバのオフセット ( 先頭要素 ェクト」という場合には関数は含まれない からのアドレスの差分 ) を知りたいことがあ る。このようなときのために , ANSI C て、は とに注意されたい ) 。 int や char, あるいは d offsetof マクロが用意されている。これは s ouble などの単純な型て、あってよいのはもち tddef. h の中て、定義されると規定されている ろんのこと , 配列や構造体 , さらには後述 X. fname , P (ANSI 4.1.5 ) 。ところが , ANSI C の標準 q &x. fname [ 0 ] ; する共用体て、あってもかまわない。さらに 的な教科書て、ある K&R 2nd て、は , なぜかラ は任意のポインタも当然許される。 それらのメンバは , 構造体に含まれない こて、 , 構造体 x のメンバ fname は配列て、 イプラリを解説している付録 B のセクション 通常の変数と同等の資格を持っていると考 あるから , p に代入している右辺の値は fna から stddef. h の解説が落ちている (ANSI て、は me の先頭要素へのポインタとなり , 結局 p , 15 の標準ヘッダを規定しているが , 同書て、 えてよい。すなわち , 通常の変数に対して 行うことが可能な操作は , すべて同様に構 解説しているのはそのうちの 12 種だけて、あ q ふたつのポインタ変数に代入されるのは同 造体のメンバに対しても行うことがて、きる。 じポインタ値となる。 る ) 。このため , offsetof が ANSI に含まれて 単項演算子、、 & クを適用して , 特定の構造体オ ところて、 , p および q に代入されるポイン いるのはあまり知られていないようだ。 と プジェクトに含まれる特定のメンノヾへのポ タ値は , アドレスとして ( 型情報を無視して ) もあれ , offsetof を使って , 先の構造体 stru インタを生成することも可能て、ある。また , 考えると , 構造体 x そのもののアドレス , す ct example のメン '*status のオフセットを 構造体のメンバが配列て、ある場合 , 構造体 なわち &x と同じて、あることに注意された 求め , それを i に代入するには次のように記 名にドット演算子を用いてメンバ名を指定 stddef. h で定義されている 0 幵 setOf マクロ例 し , それに添字づけを行っていない場合 , あるいは , 単項の、、 * クによる間接参照を行 っていない場合といってもよいが , その場 合には , 通常の配列と同様にポインタ生成 (pointer generation) のルールが適用され る。具体例を上げると次のような場合て、あ い ヾこ struct example { fname [ 64 ] ; Char unsigned long SIZe , int status , Char Char List #define offsetof(s—name, ー皿船 ) ((size—t)&((s—name のの一 > 国 nnme) 116 C MAGAZINE 1992 4
よるものてある。なお , 以下 , 本稿中に登 場するという記法は「とは相互に 自由に書き換え可能」という意味て、ある。 たとえば今 , fileList の 3 番目の要素のメン バ fname の 8 番目の要素に文字ヾ c 〃を代入し たいとする。もっとも素直な書き方は (List 2 ー① ) のような形式てあろう。 このように添字が 2 か所て、施されることに 注意されたい。左側の添字は外側の構造体 の配列 fileList の要素を指定し , ヾ . fname" に よってその中のメンバ fname を指定し , 右側 の添字が文字配列 fname の中の要素を指定 する。 余談だが , 実はこの struct baz とメモリ上 の配置が実質的に同じデータ型は , 2 次元配 列ても実現てきる。 char fiIeList [ 5 ] [ 64 ] ; このように ( 1 次元の ) 配列だけをメンバに 含む構造体の ( 1 次元の ) 配列は , 実質的に 2 次元配列と同じて、ある。もっと一般的にい えば , n 次元の配列だけをメンバに含む構造 体の m 次元の配列は , 実質的に ( n 十 m ) 次元 の配列と同等にみなすことがてきる。もち ろん , 構造体の中にほかのメンバが加わっ た場合には , 配列とはメモリ上の配置が異 なってくるし , 構造体てなければ実現て、き ないデータ構造となる。 話を戻して , ( A ) て、示した配列の添字づけ とポインタの書き換規則を用いて List 2 ー① を書き換えてみよう ( カッコは不要てあれば 省略する ) 。まず fiIeList [ 2 ] のほうを書き 換えてみる (List 2 ー② ) 。 今度は , List 2 ー①から fname [ 7 ] のほう だけを書き換えてみよう (List 2 ー③ ) 。 続いて , 両方をポインタ形式に直すと Li st 2 ー④のようになる (Fig. 2 参照 ) 。 カッコが多くて複雑だが , 演算子の優先 こに記したカッコはす 順位の関係から , べて必要てあり , 省略することはてきない たとえば , List 2 ー②においては , * (fiIeList 十 2 ). fname [ 7 ] 120 C MAGAZINE 1 2 4 て、はなく , ( * (fileList 十 2 ) ). fname [ 7 ] となっている。これもすて、に述べたように 単項の演算子 * クよりも、、 . クのほうが優先順 位が高いためてある。 一般に , ある構造体へのポインタ p があっ たとして , そのポイント先の構造体のメン バ m をアクセスするためには , 、、 ( * p). m 〃と 記す必要がある。単に * p. m 〃て、はだめてあ る。後者は「 ( 構造体 ) p のメンバ m がポイント している先」という意味になってしまう。意 図しているのは「 ( 構造体への ) ポインタ p が ポイントしている先の構造体のメンバ m 」て、 ある。前回にも述べたが , ヾー > クという演 算子を用いると , この煩雑なカッコを多少 なりとも除去て、きる。すなわち , の関係が成立するからてある。この関係を 両者は , 表現は違うが同じ意味て、あるこ の要素は実際に存在しているものとする ) 。 ポインタ」て、ある ( 話を簡単にするためにそ すなわち a の先頭要素から n 個先の要素への れたポインタがポイントしているところ , あり , 右辺は「 a からポインタ生成て、生成さ 辺は「配列 a の添字 n の要素へのポインタ」て、 a が配列名 , n が整数型の式とすると , 左 行ってみよう。 の関係が成立することを用いて書き換えを さらに ぞれ List 2 ー⑤ , ⑥のようになる。 利用して List 2 ー② , ④を書き直すと , それ Fig. 2 構造体のアクセス形式のバリエーション ( 1 ) fileList[2]. fname[7] = fname[7] を展開 ( * (fiIeList 十 2)). fname[7] = fiIeList[2] を展開 fname[7] を展開 * (fiIeList[2]. fname 十 7) = fiIeList[2] を展開 * ( ( *fileList 十 2)). fname 十 7) ='c'; 配列名から先頭要素へのポインタの変換を明示 * ( & ( * (&fiIeList[O] 十 2)). fname[O] 十 7)=