プログラム言語探訪 C
よく「C言語」と呼ばれますが、実際には「C」だけでいいらしいという言語です。名前がシンプルすぎるのと据わりが悪いのでC言語と呼ぶのが普通かな。
そして作者が触れた三番目のプログラム言語で、今もC++と共に付き合いが続いている言語です。
歴史をたどると1972年にベル研究所で開発された汎用言語で、OSを記述するために作られたという経緯がある……とよく言われてますが、実際にはOSのコマンドプログラムを始めとする周辺プログラムを作るのが最初の目的だったとか。その後OSを記述するようになったのは確かで、今でもいくつかのOSがハードをゴリゴリ触るところ以外はC言語で書かれていたりします。
特徴としては、とにかく仕様が単純。ただし、命令語や演算子をあれこれ組み合わせることが簡単なので、書き方によっては「これ、どういう動作するんだ?」という難解なものができあがったりするし、「このような書き方をした場合の実行結果は未定義である」なんてのもあります。
例えば、こんな感じ。
int a;
printf( "%d\n" , a );
この実行結果、ゼロになる保証は一切ありません。基本的に宣言した変数は修飾語やコンパイラオプション――あるいは何らかの厳格な処理をするコンパイラ――の指定がない限り、中に何が入っているかは未定義です。実際上記の内容を実行すると、実行するたびに結果が変わったりします。が、そういう仕様なので文句を言ってはいけません。なお、コンパイラによっては初期化していない変数の値を使っているという警告が出たりします。
あとはこんなのもあります。
printf( "%d %d\n" , funcA() , funcB() );
ここを実行するとき、funcAとfuncBのどちらが先に実行されるかは定義されていません。極端な話同じコンパイラでもオプションをつけると順序が変わったりします。
まあ、この程度なら序の口です。よくあるのがこんなの。
if( strcmp( str1 , str2 ) == 0 && ( fp2 = fopen( filename2 , "r" ) ) != NULL )
IF文の条件式にAND演算子で二つの条件を記述していますが、左側にあるstrcmpで文字列を比較し、一致したときだけ、fopenでファイルを開いてその結果を代入しつつ、開けたかどうかのチェックをするという処理です。これ、strcmpで文字列が不一致だった場合、fopenは実行されません。俗に言う副作用のある書き方、という奴です。このくらいだと影響は小さいですけどね。キチンと言語の仕様書に記載されているので、「なんでこんな動作をするんだ?!」と文句を言われても、です。
さて、C言語と言えば「ポインタ」という鬼門があるそうです。このポインタという概念が理解できないとC言語を理解するのはほぼ絶望的だとか言われていて、一部では「ポインタがある時点で欠陥言語」という人もいるとか。プログラム、変数がメモリに確保されてその中で動いている、という概念をしっかり押さえれば簡単な概念なんですけどね……
int a[10] として宣言した後、a[5]と*(a+5)が意味も実体も同一ということ。
これは型がどうあろうと同じで、ユーザが定義した型(構造体とかも)でも全く同じ。
もう少し言うと、文字型(char)の配列s1を宣言して(大きさは大きめに)、その中に"abcdef"を代入しようとした場合、
s1= "abcdef";
これはダメ。だいたいの場合、コンパイルが通りません。キチンとこう書かなければ。
strcpy( s1 , "abcdef" );
当たり前の話ですが、C言語には「文字列型」がないから代入もできないと言うこと。ところが、こういう書き方はできるんです。
char* s1 = "abcdef";
ここで、多くの人が挫折するんだとか。そして文字列型がないから比較もできないので、
if( s1 == "abcdef" ) ……
このif文の条件式は何をどうやっても成立しません。先ほどの宣言と同時に代入しているパターンであっても。正しい書き方はこちら。
if( strcmp( s1 , "abcdef" ) == 0 )
これなら大丈夫。という感じ。
ここまでは「文字列の扱い方」の範疇なので、ポインタどうこうではないのですが、ポインタをぐりぐりやり出すとこんなこともできる。
if( strcmp( s1 + 3 , "def" ) == 0 )
これ、成立しちゃいます。そして、もっと言うと、こういうこともできる。
if( strcmp( s1 + 3 , "abcdef" + 3 ) == 0 )
どちらもs1の四文字目以降が "def" だと条件式が「真」になるのでif文の中に入って行きます。
ここまで来ると極端な例になってしまうんだけどね。ただ、少なくとも言えることが一つ。ポインタと配列は同じ事を指してるわけではないのでまとめて扱おうとするな、と。
ポインタ周りで色々使うことが多いのは文字列操作で(※作者調べ)、それ以外でポインタを積極的に使う事ってそれほど無いんじゃないかな?なので、文字列操作の時のことだけ頭に置いておけばいいはず。ライブラリとして必要な場合(ファイル操作時のFILE構造体へのポインタなど)以外では、ほとんどポインタがどうこうって気にしなくてもいいはずなんだよね。
そもそもC言語の文字列操作が貧弱なのがいけないとも言えるのか。「文字列型」という変数型が無いから。あと、文字列関連のライブラリも、微妙にかゆいところに手が届かなかったりして不便です。
と、ディスっているように見えるかもしれませんが、ポインタで文字列をガシガシ操作できるようになると、他の言語よりも高速で処理できるようになるという不思議。
文字列型のある言語で色々処理していたものが、どうしてもメモリ不足と処理が遅すぎるということで「なんとかしてほしい」と頼まれ、C言語でポインタ操作でうまいこと処理すると十倍くらい速くなったりしました。GB単位のテキストファイルを処理するときに、某びじゅあるなべーしっくでやったら三十分かかっても終わらない処理がC言語で書いたら二分で処理してくれたという……
さらにC言語のプログラム記述はいわゆるフリーフォーマットと呼ばれる奴で、一定のルールにさえ沿っていればあとは自由に書けます。
例えば
printf( "hello\n" );
これ、このくらい分解しても平気です。
printf
(
"hello"
"\n"
)
;
もちろんこのくらいは序の口で、もっと行けます。
p\
r\
i\
n\
t\
f
(
"h"
"e"
"l"
"l"
"o"
"\n"
)
;
これくらいの自由度があるおかげ(?)で、「見やすいソースコードとは」という終わりのない議論が繰り広げられていたり、空白を詰められるだけ詰めてギチギチにして真四角にしてみたり、何かの図形にしてみたりというお遊びもあったりします。世界規模のコンテストもあったりして作者もチャレンジしてみたのですが断念。レベルが高すぎてついて行けませんでした。
そして歴史も長い言語ということもあって、それこそあらゆる環境向けにコンパイラが用意されていたりして、
「コンピュータにやらせたいことはCでだいたい記述できる」
なんて意見もあったりします。いわゆるオープンソースと呼ばれるソフトの大半がC(またはC++)で書かれていたりします。あとは、人気のプログラム言語ランキングなんてのが定期的に発表されますが、C++と共に常に上位。過去に上位にいた言語は今やすっかり下位に落ちていたりするのに、安定して上位。今後も使われていくのでしょう。
ちなみに作者とC言語の付き合いですが、大学に入った頃に自分で買ったコンパイラがC++コンパイラでして、当時はC++はちょっと、という感じだったのでCコンパイラとして使ってました。OSのシステムコールを叩いてみたり、BIOSやらI/Oポートを使ってハードウェアを操作してみたりとか色々とやりました。何しろ、C言語は一部の命令や演算子はそのまま機械語になるという特徴があったりして、アセンブラ代わりに使えるという高水準言語なのですから。
おそらくこの先、量子コンピュータが主流になって、という時代、つまり今のノイマン型コンピュータが使われなくなるまでの間はC言語って使われ続けるんでしょう。
そんな言語。それがC言語です。
本文中のソースは見やすくするためスペースを多めに入れています。