ブロックの先頭以外での変数宣言について

今日の元ネタは以下のページです(古い記事ですが)。
blog.jnito.com

まあ大筋で異論はないのですが、以前からあまり納得がいっていないのが、冒頭の
「1.使われるローカル変数をすべてメソッドの最初に宣言する。」
です。確かにC90までは、ローカル変数はブロックの先頭でしか宣言できませんでしたし、Cのスタイルを引き継いだ後発の言語、C++とかJavaとかC#とか、あるいはCでもC99からは、ブロックの途中でも変数宣言ができるようになっています。それは、その方がよい、と言語設計者が判断したからなのでしょう。しかし――
私自身、ブロック途中での変数宣言を使わないわけではありません。しかし、「使われるローカル変数をすべてメソッドの最初に宣言する」ことが悪いことであるかのように言われると、反論したくはなります。そんなことを気にする時点で、メソッドが長すぎやしないかと。
「使われるローカル変数をすべてメソッドの最初に宣言する」ことが悪いと主張する人は、変数を使う直前で宣言することで、変数のスコープを狭くすることができると言います。でも、「メソッドの先頭でちょっとだけ使う変数」は結局メソッドの最後までのスコープを持ってしまうわけで、変数のスコープを狭くする効果を期待するとしても、ずいぶん中途半端な話のように思えます。それぐらいなら、C90でも普通に使える、ブロックで囲む方法でスコープを絞る方が、なんぼか筋が通るように思います。
あと、「変数を使う直前で宣言する」というスタイルは、ブロックがスコープを制御するC起源の言語とどうも相性が悪いようにも思います。これはJavaの話になりますが、(try with resources導入前の)Javaで、try節の中で何かのリソースを生成するのでtry節の中で変数宣言して、finally節でリソースの解放処理を書こうと思ったらその変数が見えないので仕方なくtryの外に出して、さらに変数が初期化されていないとエラーが出るので仕方なく宣言時にnullを代入して、せっかくコンパイラが初期化されていないエラーを出してくれているのにこれじゃ意味ないじゃん、と悲しくなったことはないですか。
まあ、こんなことを言ってる私はレガシープログラマなのかもしれませんが、レガシープログラマならCだよね! ということで宣伝です。
今時GDIでWindows用ドローツールを作ってるC言語ポインタ完全制覇第2版発売中ですよ!

ドローツールの画面はこんな感じです。
f:id:kmaebashi:20171220010458p:plain
残念ながら紙面の都合で全ソース掲載とはいきませんでしたが、これぐらいの絵が描ける、実際に動作するドローツールを題材にしている時点で、インベーダーを題材にしながらこれじゃコードは書けないぞ、という本よりははるかにマシな本であろうと自負しております。

C99の可変長配列(VLA)はどれぐらい便利なのか

いわゆるANSI C(C90)では、配列のサイズは定数でなければなりません。つまり、配列を宣言する際には、

int a[10];

のように[]の中には定数を書く必要があり、

int a[size];

のように変数を書くことはできません(gccでは昔からできたりしましたが、それはgcc独自拡張です)。
しかし、C99からは、可変長配列(VLA: Variable Length Array)の機能が追加され、配列の要素数に変数(というか定数でない式)が書けるようになりました。
ただ、VLAが使えるのは自動変数(staticでないローカル変数)だけです。まあ静的配列の要素数が可変というのもいまいち意味が分からないので無理はありませんが、一時的にしか存在できない自動変数の配列のサイズが可変にできたところで、あまり便利じゃないのでは、とも思えます。まあ、実行時にしかサイズが決まらない一時的な作業バッファとかにはよいかもしれません。今時Cを使っている環境だと組み込み系とか多そうで、スタックのサイズ制限がきつかったりしてあまり気楽には使えない、ということもありそうです。
ただし、VLAの構文は、たとえばmalloc()で確保した領域にも使えます。
たとえばオセロでも囲碁でもマインスイーパーでもいいのですが、何らかのゲームの「盤面」を2次元配列で確保するとします。オセロの盤面は普通は8×8ですが、もっと広い盤面も選択できるようにしてもよいでしょう。そういう場合は、縦横が可変長の2次元配列が欲しくなります。しかし、ANSI Cでは、縦横可変の可変長配列を作ることはできません(ポインタを駆使して、board[x][y]の形式でアクセスできる可変サイズの領域を確保することはできなくはないですが)。
VLAであれば、たとえば盤面の1つのマスをint型で表現するとして、以下のように書くことで、size×sizeの2次元配列を確保できます。

int (*board)[size] = malloc(sizeof(int) * size * size);

もちろん、盤面の各マスは、board[x][y]のようにしてアクセス可能です。
これを関数の引数として受け渡しするときは、受け取る側の関数のプロトタイプは以下のように書けます。

void func(int size, int board[size][size]);

これは、

void func(int size, int (*board)[size]);

シンタックスシュガーです。私はこのシンタックスシュガーはあまり好みではないのですが、さすがにこのケースでは、前者の例の方が意図をよく表していてわかりやすいかなあ、と思います……
さて、宣伝しますよ。
VLAをはじめ、C99の機能についてもいろいろ解説した「C言語 ポインタ完全制覇」改訂版発売中ですよ!

なぜあなたは「配列へのポインタ」を学ぶ必要があるのか

かつて(20年くらい前)一緒に仕事をしていた人が、こう言ったことがあります。

はっきり言って、Cプログラマ過半数は、「配列へのポインタ」を理解していないと思う。

まあ「過半数」かどうかはわかりませんが、実際、それなりに長くCを使っている人でも、「配列へのポインタ」を理解していない人はいるように思います。以下、一例。

きくT(2/10 ビッグアップル) on Twitter: "んー、2次元配列を1次元にキャストして関数に渡すことはできるんだけど、1次元配列として受け取った関数側で2次元配列にキャストする方法はあるのか?"
f:id:kmaebashi:20171217185156p:plain

もちろん菊池誠先生は物理学者であり、Cは専門ではないでしょうから、このことをもって菊池先生のことをとやかく言う意図はありません。

それにしても、リプライ中にある以下の記述は、やはりCの初心者の方にとっては「わけがわからない」ものであるかと思います。

Int (*b)[5] = (int(*)[5])a;

int (*b)[5]」なんて、こんなマニアックな書き方使わないよ!

と思う人もいるかもしれません。でも、現に菊池先生が困ったように、これを使う機会は、確実にあるものです。

「配列へのポインタ」を使う典型的なケースは、「多次元配列を関数の引数として渡す時」です。たとえばオセロの盤面をintの2次元配列(int board[8][8];)で表現するとして、これを引数として受け取る関数のプロトタイプは、たとえば以下のようになります。

void func(int (*board)[8]);

いや、そんなマニアックな書き方しなくても、

void func(int board[8][8]);

とか

void func(int board[][8]);

とか書けばよいのでは? と思う人もいるかもしれません。しかし、こうして受け取った引数を別の変数にコピーしようとした場合や、malloc()で動的に多次元配列を確保したい場合は、「配列へのポインタ」を宣言することを避けることはできません。
たとえばオセロの盤面をmalloc()で確保するなら、以下のように書くことになります。

int (*board)[8] = malloc(sizeof(int) * 8 * 8);

で、わざわざこんなの書いたということはもちろん宣伝なのですが、「配列へのポインタ」はもちろん、Cの配列とポインタについて徹底解説した「C言語 ポインタ完全制覇」改訂版発売中ですよ!

ちなみに旧版の画像はこちら。この画像を見せると「あっ、見たことある!」という方も結構いらっしゃるので。
f:id:kmaebashi:20171217195236j:plain

C言語 ポインタ完全制覇 改訂版が発売されます!!

2001年の発売より、実に17年、第18刷を数えるロングセラーとなった「C言語 ポインタ完全制覇」の改訂版が発売されます。

f:id:kmaebashi:20171203200126j:plain

 

私の作成した紹介ページはこちら。

「C言語 ポインタ完全制覇(第2版)」書籍情報

技術評論社さんの紹介ページはこちら

新・標準プログラマーズライブラリ C言語 ポインタ完全制覇:書籍案内|技術評論社

Amazonでは現在予約注文が可能です。

https://www.amazon.co.jp/o/ASIN/477419381X/gihyojp-22

技術評論社さんでの電子書籍版(PDF)販売、およびKindle化の予定もあります。

 

「なんでいまさらCの本?」と思う人が多いかもしれません。私自身そう思わなくもないので当然です。しかし、現在でも、WebやTwitterで検索してみると、Cのポインタで苦労している人はまだまだたくさんいるようです。ていうか著者からすると、最近書いた「Webサーバを作りながら学ぶ 基礎からのWebアプリケーション開発入門」よりも、17年も前に書いた「C言語 ポインタ完全制覇」の方がTwitter検索するとよっぽどたくさんかかってくる、という、喜んでいいのか悲しむべきなのか、という状況がずっと続いていたりします。

旧版「C言語 ポインタ完全制覇」は、自分で言うのも何ですが、Cのポインタおよび宣言の構文について、当時としては画期的な説明をした本であったと思っています*1。最近でも時々、「Cの宣言は英語順で読むといいよ!」という記事がはてなブックマークに上がってきたりしますが、この「英語読み」を日本で最初に言い出したのは、おそらくこの本(の原型となったWebページ)です*2

 Cのポインタで悩んでいる人も、Cのポインタが「わかっているつもり」の人も、だまされたと思って上記書籍情報のページくらいは読んでみてください。

今回の改訂では、旧版と大きく構成を変えたわけではないですが、さすがに17年も経てば直すべきところも出てきます。以下のような点で加筆・修正を行っています。

  • C99の可変長引数(VLA)やC11のライブラリに言及
  • ASLRやDEPといった今時の機能にも言及
  • サンプルプログラムの動作環境の64bit化
  • 1章に、「メモリとアドレス」の項を追加。17年前にCプログラマを志す人ならある程度前提にできたかもしれませんが、今時はそうもいかないと思われるので。
  • 「補足」を目次に載せました(これはご要望が多かった!)。
  • その他、細かい修正。

何しろ旧版が323ページのところ、今回は367ページありますので、相応の書き足しを行ってます。旧版をお持ちの方も、ぜひどうぞ。 

*1:と言いつつ、実のところ文法通りの説明をしているだけなので、「なぜ私以外の人がもっと早くこういう本を書いてくれなかったんだろう」というのが正直な気持ちでもあります。

*2:たとえば「エキスパートCプログラミング」にも宣言を読むダイアグラムはありますが、いきなり日本語を組み立てているので末尾から文章を作るという妙なことになっています。どうして訳者注くらい入れてくれなかったんだろう……

車道を走ってきた自転車はどこで信号待ちすべきか

最近はクロスバイクとかロードバイクとか、あるいはママチャリでも、車道を走る自転車が増えている気がします。自転車は、車道を走った方が、歩行者を脅かさないというだけでなく自転車自身にとっても安全なので、これはよい傾向だとは思うのですが。
そうやって車道を走ってきた自転車は、赤信号の時、どこで信号待ちすべきでしょうか。
答から先に書けば、停止線で止まるべきです。当たり前です。

f:id:kmaebashi:20171127001241p:plain

ところが、実際に道路で見てみると、横断歩道を越えて、自転車横断帯の前まで行って待とうとする人が多い。非常に多い。

f:id:kmaebashi:20171127001559p:plain

これだと、青信号で横断歩道を渡っている歩行者の中を突っ切ることになります。これが正しいわけがありません。

ただ、気持ちとしては、わかる部分もあります。なにしろ交差点にはわざわざ自転車の絵をペイントした自転車横断帯があり、自転車はここを渡れと法律で定められている。ここを渡れというのなら、その手前で待つべきなのでは? と思うのも無理はありません。実際、歩道を走ってきたママチャリなら、自転車横断帯手前で待つのが正しいのでしょう。

しかし、車道を走るなら、停止線で止まるべきです。そうでないと上の図のように歩行者の中に突っ込むことになります。

名古屋の街を走っていて、自転車横断帯手前で待とうとして横断歩道に突っ込む自転車があまりに多いので書きました。彼らに悪意はないのだと思います。しかし、歩行者から見れば、「暴走自転車が歩道に突っ込んできた」と見えることでしょう。単なる知識不足から、歩行者と自転車乗りの間で無駄な衝突(物理的な衝突だけでなく、感情的なものも含めて)が起きるのは、双方にとって不幸かと思います。

で、信号が青になった時、どう走るべきか。常識的に考えればまっすぐ走ればよいはずですが、自転車横断帯がある場合そこを走ることが法律で定められて…… という話は、面倒なのでお茶を濁しておきます。いやほんと、自転車横断帯が歩道から生えているというのは、日本の、自転車に関する法律の矛盾の象徴だと思うことですよ。

プログラミング言語C(K&R)邦訳のバージョン違いについて

プログラミング言語C(通称K&R)の邦訳版と言えば、

  • ANSI C対応以前のもの。白い表紙。
  • 第2版。ANSI C規格準拠。薄緑の表紙。
  • 第2版訳改訂版。訳だけ改められた。白い表紙。

 の3種類だと思っていたのですが、第2版訳書改訂版に、私が持っているのとは異なる(古い)バージョンがあるらしい、ということをある方から聞きました。

私が持っている、訳改定前(薄緑表紙)の奥付:

1989年6月15日 初版1刷発行

1992年2月20日 初版101刷発行

 

私が持っている、訳改定版(白表紙)の奥付:

1989年6月15日 第2版1刷発行

1997年5月1日 第2版211刷発行

 

この奥付を見るだけでも、1989年6月15日に出たのは初版なのか第2版なのか不安になりますが……

情報をくださった方が持っているものは、訳書改訂版の「初版156刷 1994年3月10日付」だそうです。時期的には、訳改訂版でありかつ私の持っているもの(211刷)よりは前のもの、とういうことになりますね。

で、気になるのは、拙著「C言語 ポインタ完全制覇」にも引用した(p.以下の箇所の訳です。

原文:

When an array name is passed to a function, the function can at its convenience believe that it has been handles either an an array or a pointer, and manipulate it accordingly.

時系列で並べると、

訳改定前(薄緑表紙) 1992年2月20日 初版101刷:

配列名が関数に渡されるときに、関数ではそれが配列として渡されたのかポインタとして渡されたのかが適当に判断され、それに応じた取り扱いが行われる。

情報をいただいたもの。1994年3月10日 初版156刷 :

配列名が関数に渡されるときに,関数ではそれが配列として渡されたのかポインタとして渡されたのかを適当に判断して,それに応じた取り扱いをしてよい.

訳改訂版(白表紙) 1997年5月1日 第2版211刷 :

配列名が関数に渡されるときに,関数ではそれが配列として渡されたのかポインタとして渡されたのかを都合のよい方に判断して,それに応じて操作が行なわれる.

普通は、増刷の度に誤訳等を直していくのだと思うのですが、ここに関する限り、3番目の「第2版211刷」は、「初版156刷」より、訳改訂版以前の「初版101刷」に先祖返りしているように見えます。

 

ところで、K&Rの訳書改訂版が出る前に松井潔さんにより作られた誤訳の一覧があり、今でもvectorからダウンロードできます。訳書改訂版はかなりこれに沿った修正が行われています。

K&R 2nd. 邦訳書の正誤リストの詳細情報 : Vector ソフトを探す!

 この正誤リストにおいて、上記の箇所は、以下のようになっています(後続の1文を前橋にて削っています)。

訳書 ・・それが配列として渡されたのかポインタとして渡されたのかが適当に判断され、それに応じた取扱いが行われる。

正 ・・それが配列として渡されたのかポインタとして渡されたのかを適当に判断して、それに応じた取扱いをしてよい。

どうも、訳書改訂版作成時に、松井潔さんの正誤リストを元に修正し、それをまた後に戻したように見えます。

訳としては、私は英語が苦手なのでアレなんですが、

『主語は「the function」であり、canの後に、believeとmanipulateが並列で続いている』

と考えると、松井潔さんの訳でよいように見えます(can believeとmanipulateが並列なら、初期訳でよいのかな)。ただ、「それに応じた取り扱いをしてよい.」という言い方だと、日本語ではこの部分の主語が「プログラマ」であるようにも読めます。そして、元々この文章は原文からして意味不明なのですが(ポインタ完全制覇にも「まるで意味不明」と書きましたが)、この部分の主語を「プログラマ」だと解釈すれば、なんだか意味が通じるような気がします。プログラマは、ポインタとして渡された引数sについて、*(s+i)のように書くこともs[i]のように書くこともできるからです。情報をくださった方も、そう解釈できるのでは、と考えたそうです。

しかし原文では主語はやっぱり「the function」なので、だから戻したのかなあ、とも思えます。その場合、やっぱり「まるで意味不明」であると私には思えるのですが。

 

はてなブログを開設しました

はてなダイアリーは新規募集を停止したそうだしはてなブックマークのホットエントリーに上がってくる頻度も減ってきた気がするし、ということで、はてなブログを開設しました。
http://kmaebashi.hatenablog.com/
K.Maebashi's はてなブログ
今後は原則として新規記事はそちらに上げます。

よろしくお願いいたします。