スクリプト言語ばかり使ってたプログラマによる書評 Head First C
長らく「どうせCSやってないし、C言語はとりあえず勉強しなくていいや」という主義のweb系エンジニアをやっていました。しかし、ひょんなことから仕事でObjective-Cを書く必要が出てきたので、長らく敬遠していたC言語の勉強をちょっとだけしてみました。
読んだのは友人に勧められたこちら。
読み終えてみて、webエンジニアとしてとてもいい体験だったなぁ、と思ったので書評を書いてみました。
ちなみに、C言語を学ぶことのメリットについてつらつらと書くに止めるので、C言語の仕様については一切書きません。
気になる人はオライリーにレッツ課金。
前提: どんな人に向けた本だった?
・C言語勉強してみたいけどモチベーションがいまいちわかない人
・「スレッド プロセス 違い」でググっては忘れてまたググってを繰り返している人
・ポインタって響きがなんか怖いので近づきたくない人
・ファイルストリームってなんだ? と疑問に思いながらなんとなくスクリプト言語でファイル読み書きしている人
こんな人には非常におすすめです。大体今までの私です。はい。
逆に、以下みたいな人には向いていないかもしれません。
・C言語についての新しい知識を得たい人
・仕事でC言語をすぐに使う予定がある人
・OS作りたい人
あまり踏み込んだ話題は出てきませんでした。
C言語の取っ掛かりを掴むことに特化した本で、この本の知識だけで何か作り切るのは無理だな、とは感じる本でした。
しかし、その取っ掛かり本としての出来が非常によかったです。なので、以下につらつらとそのメリットを連ねて行こうと思います。
その1 高級言語の低レイヤ呼び出し系のAPIが覚えやすくなった
C言語はrubyやjavascriptなどの高級言語と違い、極めて簡単に低レイヤの処理を記述できます。
ディスクプリタテーブル、標準出力と標準エラー、パイプとリダイレクト。
ダイナミックライブラリとスタティックライブラリ、ストリーム、ヒープメモリとスタック。
そしてみんな大好きスレッドとプロセスについて。
必然、C言語を学んでいるとそれらの取り扱いについて学ぶ事が多く、自然とこれらに対する認識が一段階深まりました。
これらのAPIは、RubyやNode.js、シェルなどでも提供されているものがほとんどです。しかし、C言語での体験は、高級言語から扱う物とは一味違いました。
C言語での低レイヤの処理は、これらのAPIについて極めてシンプルな抽象化しか行われていません。大体のカーネルがC言語で書かれていることも相まって、「実際にどのような処理がカーネルでは行われているんだろう」などと想像する事が用意です。
例えばスレッドとプロセスについて。
Head First C ではexec()やfork()のようなプロセス操作の基礎や、POSIXスレッドライブラリを使ったミューテックスの基礎などを学ぶ事ができました。
これらをそのまま実務で使う事はそうそうないにしても、「このAPIは、最終的にCがあのAPIを呼んでいるんだな〜」と想像力を働かせる事ができるようになったのはとても良い体験でした。学んでまだ間もないため実感した事はないですが、なんかしらのバグやトラブルに見舞われた際、その解決するための情報の引き出しが多くなったと感じています。
これでもうマルチスレッドとマルチプロセスがごっちゃになって恥をかくことはないと思います(多分)
その2 C言語から始まった文化を理解できた
この本を読むまで、私はtypescriptの union type(共用型)がなぜそのような名前で呼ばれるのか理解ができませんでした。
type StringOrNumber = string|number
こんな、string型かnumber型の両方を許容する型です。
私はこの型がずっと「何を共用しているんだ?」と疑問でした。
どちらかと言うと「論理和型 (要するにor型)の方が実態として合っていないか?」などと思っていました。
この疑問への回答は、このHead First Cに記述されていました。
unionは、定義内の1つのフィールドだけを使います
unionがある場合、コンピュータは最大のフィールドに足りるだけの空間を与え、どの値を格納するかは利用者に任せます
unionは、複数の異なるデータ型をサポートする変数を作成する手段に過ぎません
(Head First C の5章から)
つまり、unionというのはC言語の仕様の一つで、「違う型のデータを、同じメモリの領域に格納できるようにし、共用管理すること」なのです。
(説明下手すぎるので、気になる人はHead First Cの5章ページを読んでください)
typescriptは基本的には実行前にjavascriptにトランスパイルされるため、型宣言はメモリの共用管理をする宣言にはなり得ません。javascriptは動的型付け言語なので、ランタイム上では全ての変数が全ての型を許容する共用型と言えるでしょう。
しかし、「複数のデータ型のデータが入りうる型」というものを表現するのに、このunionという抽象は極めてわかりやすいです。
つまり、typescriptの共用型は、C言語のunionを起源として名前がつけられたのではないかと思われます。そうでなくてもこの仕様を知ることにより言葉のイメージと実態との差がなくなりました。
(私はCS不勉強なので、詳しい経緯などわかる人いたら教えてください)
このような「C言語から継承されて決まった仕様」は他にも多いような気がします。
JavaのString型が不変である理由、連結リストのやや分かりづらい仕様、使い道がいまいちピンとこなかったタプル型(ずっと手抜きしないでobject返せよ、と思っていた)などの作りについて、だいぶ腹落ちしました。
当たり前すぎてあまり考えませんでしたが、C言語の仕様はのちに開発された高級言語に対して極めて大きな影響力を持っています。
プログラミング言語の源流、現代でも使われる古典と言ってもいいかもしれません。
一度C言語を学んでおくと、このような「なんでこんな言語仕様になっているんだ?」みたいな疑問が減り、「丸暗記だと何度覚えても忘る」系の分かりづらい仕様の理解を進める事ができるなーと感じました。
その3 ポインタ恐怖症を克服できる
ポインタ、なんか怖いですよね。
C言語を勉強してみるより前、私はポインタについて、二つの説を聞かされていました。
「Cはポインタが難しい」
「結局ポインタはただの参照。ビビる必要はない」
学ぶ前は「いやどっちやねん」とずっと思っていましたが、少しだけポインタについて学んだ今では「Cのポインタはただの参照だが、難しい」と思っています。
結局ポインタというのは「変数にメモリのアドレスを突っ込んである。Cはそのアドレスを使っていろいろな操作を行う」事だと今は認識しています。
「変数にメモリのアドレスを突っ込んである」という概念自体はそれこそ30分かからず理解できました。しかし、「Cはそのアドレスを使っていろいろな操作を行う」でできる事が多過ぎ、それが「ポインタ難しい」に繋がっています。
「配列変数は文字列の最初の変数を指し示すポインタ」だとか「構造体にポインタが格納されている場合と値が格納されている場合の注意」だとか、
細かなハマりポイントが無数にあり、「覚えた後に慣れる必要がある」概念が多いです。
「完全に理解しているが何もわからない」という、よくある学習曲線の山場が非常に早く来ます。これがおそらく、Cのポインタの「難しい」だと思います。言っている事は全てシンプルなのですが、組み合わさると本当に何もわからなくなります。
ただ、この学習体験は極めて有意義でした。
ポインタに関する「何もわからない」部分が「調べたらなんとか分かりそうなもの」に転嫁され、「ややこしいけど頭を使えば使えそうなもの」にする事ができました。
今後仕事でC系列の言語を使う際にビビらずに済むようになったようになったし、恥ずかしながら、ポインタアレルギーで避けていたgoなどに触れてみようかと思えるようになりました。
まとめ 断食としてのC言語
とまあ、3つほど。スクリプト言語ばっかり書いているwebエンジニアにとって、C言語の学習は非常に有意義でした。
C言語は低レイヤの処理をかなり明示的に記述できるため、パフォーマンスチューニングが得意な言語のようです。しかし、それゆえにCの言語仕様は複雑な部分も多く、複雑なコードを簡単に書くことができてしまいます。web開発などに求められるレベルのベロシティを保ちながら、バグの少ない開発を進めていくのは難しそうです。
C言語の勉強は、ある意味では断食体験のようなものでした。ガベージコレクションやString型を何も考えずに使っていた高級言語から離れ、char型配列の挙動やメモリの管理に思いを馳せてみる。高級言語はそもそもが富豪的な実装をするためのものであり、パフォーマンスを犠牲に安全性やメンテナンス性を得ているのだ、という事が改めて実感できました。
不要なパフォーマンスチューニングを行なっていないか、ロジックを無意味に難しくしていないか、高級言語を使った甲斐がある読みやすいコードを書けているかーー
日々の仕事ではそれが大事なのだということを、改めて認識できました。
新しいことを勉強してみようと漠然と考えているスクリプト言語系エンジニアの方、もし良ければC言語も選択肢に入れてみてください。
僕はすぐに何かができるようにはなりませんでしたが、プログラマとしてなかなか価値のある体験ができたと思っています。
大丈夫。オライリーの入門書だよ。