C言語の抽象機械という基本

本来C言語を扱う上で基本であるはずの「抽象機械」という概念、実は全然知られてなかったりする? という疑念から始まった色々な解説
14

発端は、ふと疑問に思って投票を募ったところから

angel (as ㌵㌤の猫) @angel_p_57

なんかC言語の話してる人たち、基本レベルからちょっと怪しいんじゃないかという気がしてるんだけど…。ちょっと投票とってみるか。 「例えばRubyがRubyVM、JavaがJavaVMという環境を想定した実行モデルになっているように、Cもある抽象機械を想定した実行モデルになっている」

2021-09-11 01:41:36

一応投票結果を画像化したもの

で、結果が出た後

angel (as ㌵㌤の猫) @angel_p_57

さて結果が出まして。まずは投票していただいて、皆さま有難うございます…というところなんだけども。でも、先に言いたい。 なんで大半の人が「ウソ」認定してるのさ。んな引っ掛けみたいなマネ、私がするわけないじゃん。どう思われてるんだよ、というのにショックを受けたのだった。 twitter.com/angel_p_57/sta…

2021-09-12 19:46:19
angel (as ㌵㌤の猫) @angel_p_57

でもって、ひょっとしたら…とは思ってたんだけど、「知ってる」の比率がとても少ない。 ※なお「当然」と書いてるけど、「知ってるけど『当然』とまでは思ってない」という人の票も含まれている これは大分マズいんじゃないの、というのが私の感覚。

2021-09-12 19:46:57

ここから抽象機械の話

angel (as ㌵㌤の猫) @angel_p_57

先に答え合わせをしておくと、これちゃんと規格書に載ってる。ドラフトの方で行くけど、C99なら open-std.org/jtc1/sc22/WG14… の 5.1.2.3 Program execution の章 ここに、"an abstract machine" の振る舞いを説明してますよ、って感じで書いてある。つまり「抽象機械を想定した実行モデル」で問題ない。

2021-09-12 19:47:52
angel (as ㌵㌤の猫) @angel_p_57

あ、ひょっとしたら「抽象機械」って訳語に違和感を感じて「ウソ」判定した人もいる…? 「機械」じゃなくて「計算機」じゃないの、とか。( でも「計算機」ってのも「計算用機械」だよね ) そこらへん、どうせ訳語の話だから厳密性なんて求めてないし求められない部分だと思うので。悪しからず。

2021-09-12 19:48:51
angel (as ㌵㌤の猫) @angel_p_57

ただ、「規格書に~」って言うと「そんなの一般の人読み解くの大変なんだから」みたいに言い出す人もいそうなんだけど。「『チュウショウキカイ』なんて専門用語出されても難しいでしょ」とかもありそうかな。 でも、そんなヤヤコシイ話をしている訳ではない。もっと単純な話。

2021-09-12 19:49:12
angel (as ㌵㌤の猫) @angel_p_57

どういうことかと言うと、アセンブラみたいなソースとバイナリで一対一対応した翻訳をやっているわけじゃなくて、「C言語のモデルになるような箱庭のようなところで処理させたのと、同じ結果を実現してくれますよ」と、そういうことを言ってる。つまり、単なる命令の置き換えじゃないってこと。

2021-09-12 19:50:03
angel (as ㌵㌤の猫) @angel_p_57

JavaVMなんかを引き合いに出してると勘違いする人もいるかも知れないけれど、「元のソースに対して中間コードを生成して、専用の処理エンジンで処理する」って話ではない。 そうではなくて、「コンパイラなんかが処理を組み立てる時にどういう判断をするか」のベースの話。

2021-09-12 19:50:52
angel (as ㌵㌤の猫) @angel_p_57

例えば、"i++" というint型変数 i に対する式があったとして、「加算処理に相当する add や inc と言った機械語命令を生成します」なんて決まりはない。それどころか "int i;" っていう宣言に対して「変数 i に相当するメモリ領域を確保します」ってことすら決まってない。

2021-09-12 19:51:09
angel (as ㌵㌤の猫) @angel_p_57

でも、処理を追いかける分には、"int i;" で変数 i の領域が「C言語としての箱庭上で確保」されて、"i++" の式では「C言語としての箱庭の中で加算処理が行われる」と考えて問題ない。実際にコンパイルしてできた exe を実行したら、そうやって処理されたのと「最終的な結果」が同じになるから。

2021-09-12 19:51:25
angel (as ㌵㌤の猫) @angel_p_57

もうちょっと別の例を挙げてみるか。 数値を出力する printi() という関数があったとして、次のソースが何を意味するか。 for ( int i=0; i<4; i++ ) { printi(i); }

2021-09-12 19:51:53
angel (as ㌵㌤の猫) @angel_p_57

C言語的な箱庭の上では、変数 i が1ずつ増えていって、その値に応じて printi() が実行される。で、i が4になったところでループが終了する。そういう風に考えて問題ない。

2021-09-12 19:52:12
angel (as ㌵㌤の猫) @angel_p_57

で、じゃあそれがどんな機械語になってるの? と言うと。 例えば、次のような(なんちゃって)アセンブリに対応する機械語で実現されていてもおかしくない。 push 0 call printi push 1 call printi push 2 call printi push 3 call printi

2021-09-12 19:52:39
angel (as ㌵㌤の猫) @angel_p_57

つまり、変数 i に対応したメモリ領域の操作もなくて、加算処理もなくて、単に 0~3 の値を引数にした4回分の printi のサブルーチンコールが行われる。そういう処理になる可能性もある。 でも、C言語的になんら問題ない。処理系がそれで「箱庭で実行したのと同じ結果になる」と判断したのであれば。

2021-09-12 19:53:02
angel (as ㌵㌤の猫) @angel_p_57

ちなみに、こういう場合にデバッガで動作を追いかけると、変数 i に対応したメモリ上のデータが無いものだから、値を調べようとしても "optimized out" になったりする。 ※最近のgccだとここら辺賢くなって、VTAという機能で「箱庭」に踏み込んで調べられるようになってるみたいだけど。 pic.twitter.com/lai0lhfQ1k

2021-09-12 19:54:21
拡大
angel (as ㌵㌤の猫) @angel_p_57

他の例としては、printf("Hello\n"); と書いたのに、機械語命令的には "Hello" という文字列のアドレスを引数にした puts() がコールされるバイナリになってました、とか。 処理系がそれで「同じ結果なんだから無問題」と判断しているなら、サブルーチン自体が別でも良いのである。

2021-09-12 19:56:00
angel (as ㌵㌤の猫) @angel_p_57

ということで、どう領域が確保されるとか、どういう計算が行われるとか、それはまず「C言語的な箱庭でどうなんだ」というのが重要で、そこをすっ飛ばして「メモリの番地が~」とか言うのは、話の順番としておかしい。 で、この箱庭的なものが「抽象機械」ということ。

2021-09-12 19:56:30
angel (as ㌵㌤の猫) @angel_p_57

「入門書に載ってないし」とか言う人もいるかもしれないけど、はっきりそれはその入門書がおかしい。 「抽象機械」という単語そのものはどうでも良いんだけど、ソース→バイナリの間に、そういう中間的なモデルがあることに触れてないのは大問題。

2021-09-12 19:57:16
angel (as ㌵㌤の猫) @angel_p_57

大体、PythonとかRubyで「オブジェクトがどうメモリに保存されて~」とかやらないでしょ。それぞれの言語の箱庭的な環境が暗黙的な前提になっているし、それで困らない。C言語だってそれは変わらないということ。 もしかして「C言語が高級言語である」ということを忘れているんじゃないだろうか。

2021-09-12 19:58:17
angel (as ㌵㌤の猫) @angel_p_57

もちろん、これまでの話は「基本の基本」の部分の話であって、それを踏まえた上で「じゃあコードがどのような機械語になるか」なんかを考えるのがダメということではない。詳しい人がいきなりバイナリレベルの話をしているのは、そういうことになる。

2021-09-12 19:58:40