見出し画像

カーニハンCを読む 第1章 やさしい入門(2)

前回の続き。
前回?
前回っていつだっけ?



変数の宣言は関数の初めに

そうなのよ。関数の初めに宣言しておかなければならなかったのよ。かつては。ところが、今ではどこで宣言してもOK………なのか?
JISX3010 P.70 「6.7 宣言」の制約にはそのようなものがなかった。
clang も、次のコードに対して何ら警告を発しない。

void test1()
{
        int i = 3;
        printf("i is %d 0x%x\n", i, i);

        if (i == 3)
        {
                int j = 4;
                printf("j is %d 0x%x\n", j, j);
        }

        int k = 5;
        printf("k is %d 0x%x\n", k, k);
}

組み込みの開発は環境がなかなか追い付かなくて、未だに古いコンパイラを使っている。PCでシミュレーションするときにはVCを使うんだが、VCはもちろん任意箇所で宣言できる。ところが、なかなかバージョンアップしないターゲットCPUのコンパイラはスコープの先頭しか許容してくれない。上記コードの i 、 j  は許されるが、 k は認めてもらえない。VCではコンパイルが通ったのに、ターゲットCPUのコンパイラではコンパイルエラーになったりする。困る。


int の精度は使用する特定の計算機に左右される

そうなのよ。
これも非常に困ったことでなんである。

最近は int 型は 32bit で安定しているかもしれないが、かつては 16bit だったり、 32bit だったりした。

16bit だって 32bit だって、「1000」は「1000」だ。 10万などというような大きな数値を扱うのでない限り問題にはならないでしょ?

そう思われるかもしれない。
それはそれで正しいのだが、組み込みの場合にはそれ以外の理由も存在する。

一つは、記憶媒体の容量の問題。
今でこそあまり問題にならなくなったが、かつての組み込みソフトの記憶容量は本当に限られていた。RAMもROMもかつかつである。油断しているとあっという間に容量をオーバーしかねない。データを記憶するのに32bitサイズを選択することはめったにない。たいていは8bit、すなわち1byteである。「char」をタイピングする頻度は極めて高い。それでも、1byteを確保するのはまだいい。1bitや2bitなどということもしょっちゅうである。1byte中に8つの情報を押し込むんである。先日もbit編集するコードを書いたばかりだ。C言語では構造体でビットアサインもできるが、これもあまり使わない。全体のサイズを必要最小限に抑えてくれるとは限らないからだ。8bitでいいのに、有り難くも16bitや32bitを確保してくれるかもしれない。切り詰めてbitアサインしている意味がなくなる。

そして、もう1つは外部CPUとの接続である。
接続方法はいろいろであるが少なくとも
 ナントカ情報は1byte、
 カントカ情報は4byte
などというようにサイズがきっちり決められる。これらの情報をアクセスするのに構造体を定義したりもするのだが、
 int、int、int………
などと呑気に並べるわけにはいかない。
 8bit情報ならchar、
 16bit情報ならshort、
というように情報ごとに必要領域を確認しつつ設計実装する。

これら2つの問題、容量の問題にしても、外部CPUとの接続にしても、情報毎にデータサイズを細かく設計する。
8bit、16bit、32bitをかなり細かく使い分ける。

サイズが16bitになるか32bitになるかわからないint

など、恐ろしくて使えない。今使っているCPUが16bitであることが確かであったとしても、である。何かのきっかけでCPUを置き換えるということもないとは限らない。変更したCPUが16bitであるとも限らない。intが16bitであることを前提にしてintを使いまくったコードを、int 32bitのコンパイラに移植するなんてことになったら、泣く。intのサイズをオプションで変更できるコンパイラもあったりするが、他力本願である。期待したときに限って裏切られる。


while の本体は1文でもよい

そうなのよ。
while の本体は必ずしも「{ }」はいらない。
例えばこんな風に。

while (i < j)
    i = 2 * i;

但し、これは一文に限る。

でもね、このコードに改修を入れようと思ったら、こんな風に書いちゃうじゃない。

while (i < j)
    i = 2 * i;
    j++;

しかし。
この「j++;」は while ブロックには入らないんである。
見た目は入るように見えるのに。
このため最近はあまり推奨されない。
必ず「{ }」で囲むことをよしとされる。

一文ならこう書くかな。

while (i < j) i = 2 * i;

こんな風に改修する人もないとは限らないけどね。

while (i < j) i = 2 * i; j++;

これもやっぱり「j++;」は while ブロックの中には入らない。誰もそうは読まないとしても。


ところで,printf はC言語の一部ではない

そうなのよ。
というセリフも飽きてきたが、そうなんである。

C言語を長く書いていると書き飽きるほどに書く(?) printf であるが、これは予約語ではなくただの関数である。

これをわざわざ取り上げたのは、先日次のような記述を見つけたからだ。

関数からの出力は「return」にすべきか
はたまた「printf」にすべきか

うーん、ちょっと違うんだけど。

関数の出力は、
  引数か
  戻り値か
  外部変数か
のいずれかの選択である。

「printf」はそれには含まれない。

何故、「printf」を含まないのか。
「printf」はユーザへの出力だからである。

ユーザとは、そのアプリケーションを使う「人」のことである。そしてそのユーザ=人への出力というのは多岐にわたる。

「printf」はターミナルに文字列を出力する。
画面にグラフィックで出力することもあるかもしれない。
あるいはWebでブラウザに表示するかも。
あるいはスピーカーから音声で出力するかも。
あるいはプリンタに印刷するのかも。
あるいはcsv形式のファイル出力なのかも。
もしかしたら、全く想定しない出力もあるかもしれない。

とにかく、ユーザインターフェース(UI)というの様々だ。しかも日々変化する。十年後には別のUIが主流になっているかもしれない。「あのアプリ、UI古いよね、書き換えようか」ということもないではない。アプリケーションを設計するにあたって、UIは別にするべきである。上手くいけばUIだけを修正して他はそのままということが可能かもしれない。


(オマケ)defineのシンボルは大文字で書くのが普通てある

う。
カーニハンCの初版からの慣習だったのか。
私は今でも受け継いでいるが、どこでもそうなんだろうか。フル大文字というだけで、定数と判断してるもんなぁ。考えてみればたったそれだけのことで、それだけの判断ができるというのは、結構便利なものだ。


以上。
あらためて読むといろいろと面白いカーニハンCであった。







この記事が気に入ったらサポートをしてみませんか?