この日記はGNSで生成しています。 |
staticの話。この場合、「static char *monthname[]」のstaticが目的としているものは、「char const *monthname[]」と同義です。やり方が違うけど、塩兄ちゃんとこも参照。
_ ROM化まで考えるなら、constを使うほうが正しいです。それも「char const * const monthname[]」と書くべきです。こう書くことで、「"Jan"・・・」とそれを指すポインタの配列の両方がROMに置かれ、アクセスはそのROMアドレスをアクセスするだけになる、というコードが出ます。
_ ですが、手持ちのコンパイラ(bcc32とcl)で試したところconstでは目的のコードは出なかったんです。どっちもROM化は考慮されていないから当然かも知れんけど(笑)。・・・と思ったら、monthstr[]の定義を関数内スコープじゃなくてグローバルスコープに置かないといけないんですな。
_ 種明かしを。staticがない場合、関数は「monthname[]に各ポインタ値を再ロードする」という初期化をまず実行します。定義付きローカル変数は関数の開始時に初期化されますから、配列の中身も毎回初期化されるということです。
_ 具体的には、monthname[]は、
_ というアクセスのされ方をします。staticが付くと、
_ となります。実行時間・メモリとも当然消費量が減ります。
_ グローバル変数として「char const * const monthname[]」と書いた場合は、
_ となります。誤って「char const *monthname[]」と書いた場合は、
_ となってしまいます。「書き換え可能データセグメント」をRAM、「書き換え不可データセグメント」をROMと読み替えると、よくわかるかと。
_ ポインタでなくstrcpyして返したのは、「引数で渡されたchar*型にstrcpyした」と書かれていたのを仕様として従ったまでです。本チャンのコードなら、必要に応じて添字範囲チェックやバッファオーバーフローチェックをする(strncpyを使う)のは当然のことです。ただ、「実処理コードよりチェックコードの方が大きくなって本来の内容がぼやけてしまう」のはよくあることで、今回はまさにそれで、内容からするとチェックコードを入れる必要はないだろうという判断で省きました*1。
*1:
でも、「配列を12個確保して添字を[n-1]にする」というセコい方法を今回とらなかったのは、ポカミス回避のひとつの方法。この手の関数の月を示す引数に誤って0を渡してしまうのはよくあるミスで、このとき[n-1]を叩いてしまうと配列外を叩くコードになってしまう。しかもこの手のバグはコードが大きくなってくると発見が困難になりがち。それなら添字1つ分余計に確保して確実に空文字列を返すほうがまだ安全。
添字チェックをちゃんとすればこんなことは考えなくてもいいんだけど、それと同時に「同程度の打鍵数でよりバグを出しにくい、あるいは出ても発見しやすいようにコードを書く」という手抜き技も重要。
実際のところ、チェックコードを的確に入れるのはかなり面倒で、わし自身は「わし以外が作用する可能性がある」部分、つまり入出力UIやpublicな関数の引数にしかチェックコードを入れないことがほとんど。もちろんライブラリやデバイスドライバのような(一般アプリでない)コードには大量のチェックコードが入ることになる。
_ しかし、最近はデータ領域どころかスタックさえ知らずにCで開発する人も少なくないからなぁ・・・^^;。\
メールはこちらへ...[後藤浩昭 / Hiroaki GOTO / GORRY / gorry@hauN.org]