[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
初回に、いくつかの典型的な処理系依存の問題を紹介しましたが、C言語の場合、 実際に使うプログラムを作ろうとすると、なぜか処理系依存の処理が必要になる というおかしな傾向があります。
ただし、C言語で、特に日本で使われている「ASCII文字」という表現は、それ ほど厳密な意味ではなく、単に1バイト文字の中の0〜0x7Fに割り当てられている ものを指すことが殆どです。日本で使われているパソコンの大部分は、実際はASCII 文字ではなくJISの文字もどきを表示するように設計されています。例えば0x5cに 該当する文字はASCIIではバックスラッシュ(\)が割り当てられていますが、JIS では円記号(¥)になっています。俗にASCII文字と呼ぶ場合は、このような差異は 無視して、漢字ではない1バイト文字の意味で使われることが多いようです。
'¥n'
で表現される文字のことです。意味は「現表示位置を次の行の最初の位置に移動
する」(JISによる定義)です。
さて、これがどのような値のコードのデータとして表現されるかという問題が あります。実はこれは簡単な話ではありません。結論からいえば、0x0a、0x0d、 またはこの二つの組み合わせとして使われることが多いようです。
0x0aはしばしばLF(line feed)と略記されます。これを単純に解釈すると改行と いう意味になるのですが、場合によっては、'¥n'と同じ働きを持つコードとして 扱われることもあるし、単に行を改めるだめ、すなわち現表示位置を現在の桁の 次の行の位置に移動する特殊コードとして扱われることもあります。
0x0dはCR(Carriage Return)と略記されます。これはC言語の表現では'¥r'に相 当するコードになることが多く、意味としては「現表示位置を現在の行の最初の 位置に移動する」ということになります。
そこで、MS-DOSでよくある解釈としては、各行の最後が0x0a、0x0dの2バイトに
なっている、というものです。これは0x0aにより次の行に移動し、0x0dにより今
いる行の先頭に移動する、と考えることができます。すると、
'¥n'
というのは一体どんなコードなのか?という疑問が発生します。
C言語は、最初UNIXの世界で発達しました。UNIXの環境下では、改行コードは0x0a だけというのが一般的で、0x0dは不要です。従って、UNIXで作成したドキュメン トを文字コード変換だけ行ってMS-DOSに持ってきたりすると、改行コードの変換 を忘れてしまい、見た目がどうもおかしい、という事件が発生することがありま す。
C言語では、単に
EOF
といえば、ファイルの終了を意味する値であり、大抵の場合
<stdio.h>
で定義された-1という値のことを意味します。この質問でいうところのEOFは、
この定義とは関係なく、Control-Zに相当する文字コードとして使われているの
で、紛らわしいですが混同しないよう注意が必要です。
一部のワープロ、エディタを使って文章を作成した場合に、文章の最後に Control-Zが勝手に付加されてしまうので、それをそのままネットで発言した場合 に、さらに、そのコードがそのまま掲示されてしまうということがあります。そ して、読む側はそのコードをログに取り込んでしまうと、テキストモードを使っ てファイルを処理しているようなソフトウェアを使ってこれを見ようとした時、 この位置から後に書かれている内容が消えたように見えてしまうわけです。
このようなファイルからControl-Zを取り除くだけのプログラムを作るのは難し くはありません。バイナリモードで問題のファイルをオープンして、順次文字を 読みだし、Control-Z以外の文字をそのまま書き出せばよいだけです。
#include <stdio.h> int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "[使い方] cutz 元ファイル名 新ファイル名¥n"); exit(1); } fp_in = fopen(argv[1], "rb"); if (fp_in == NULL) { fprintf(stderr, "ファイルを読むことができません¥n"); exit(1); } fp_out = fopen(argv[2], "wb"); if (fp_out == NULL) { fprintf(stderr, "ファイルを書くことができません¥n"); exit(1); } while ((c = getc(fp_in)) != EOF) { if (c != CTRLZ) { putc(c, fp_out); } } fclose(fp_out); fclose(fp_in); return 0; }
fopen
のモードにテキストとバイナリがあるが、これらの違いは何か。
テキストの場合、考え方としては「行」という単位が処理の単位となります。 各行は改行文字を付加した0バイト以上の文字の並びです。そこで、前述の質問に あったような、改行文字とは何か、という問題が発生することになります。また、 処理系によっては、ファイルの最後という概念が導入されていることがあります。
テキストモードでファイルをオープンした場合、読み取った行の改行文字の直 前に書かれている空白文字の並びが、データが読み込まれる時に現れるかどうか は、処理系定義になっています。つまり、もしかすると行末の空白は読み飛ばさ れたように見えるかもしれません。
この処理を標準関数だけで実現するためには、最後をカットしたいファイルを 最初から必要な所まで読み、別のファイルに書き出し、その後、書き出したファ イルと元のファイルを置き換えるという処理が必要になります。
(参考: c.l.c FAQ 19.13)
もし長さを変えずに済むのなら、ファイルを更新モードでオープンすれば、途 中の内容を変更することは可能です。
(参考: c.l.c FAQ 19.14)
printf
を使って文字を表示する場合には、俗にエスケープシーケンスと呼ばれている一
連の特殊文字列を使ってこの目的を達成できることがあります。または、textmode
のような処理系依存の関数を使って表示モードを変更できる場合もあります。
特にUNIXの環境の場合、画面表示に用いられるエスケープシーケンスは、いろ いろな端末によって異なるため、termcapやterminfoというデータベースエントリ にあらかじめ属性変更用のシーケンスを登録しておき、それを参照して文字属性 を変更するようなライブラリを使うことになります。
ですから、単純な解決方法としては、ユーザーにリターンキーまで押させて、 その後、リターンキーが入力されるまで標準入力を読み飛ばす処理を追加すると いう手があります。
c = getchar(); while ((dummy = getchar()) != '¥n') ;こうすることによって、メニューの1を実行したいのに2を押してしまったよう な場合に、バックスペースで訂正して1にしてからリターンを押す、という逆戻り が可能になります。ただし、それだけユーザーの操作が面倒になるという考え方 もあるでしょう。
どうしてもキーを押した瞬間に処理させたいのなら、各処理系に依存した入力
関数を使うしかありません。MS-DOSなら
getch()
、UNIXの環境なら
ioctl
関数で端末の状態を変更したり、cursesライブラリを使って目的が達成できるで
しょう。そもそも、最近のGUI環境であれば、これらの概念から全く独立したGUI
ライブラリが用意されていて、メニューの処理を書くことができるはずですが、
その具体的な方法は、ますますC言語とは無関係な処理系依存の話になってしまい
ます。
一般に、何も読み込む文字がない場合にも即座に処理を終えて戻ってくるよう
な入出力のことを「ノンブロッキングモードのI/O」「ブロックしないI/O」のよ
うに表現します。関数
open
には
O_NDELAY
というオプションが使えることがあります。MS-DOSのキーボードの状態を知るた
めには、biosを直接呼び出す関数を使うか、あるいは
kbhit()
のような関数が非標準関数として用意されていることを利用します。
標準入力、パイプからの入力、端末やシリアルポートからの入力をリアルタイ ムで処理したい場合に、現在バッファにデータがあるかどうかの判断が重要です。 例えば通信ソフトを作る場合に、シリアルポートに何かデータがあればそれを受 け取って処理して、何もデータがなければそれ以外の処理をする、というような 設計が考えられますが、これを実現するためにはシリアルポートを読む前に、そ こにデータが入っているかどうかを知らなければなりません。
ノンブロッキングモードでオープンしたファイルから、
fread
等の関数でデータを読む場合には、必ずしも用意したバッファサイズと等しいだ
けデータが読まれているとは限らないことに注意が必要です。場合によっては、
バッファの途中までデータが入った状態で戻ってくるかもしれません。何バイト
のデータが得られたかどうかを関数の戻り値によって確認して、必要なだけ処理
するようなコードを書くことになります。
プログラムをこのようにして部分に分けると、ある一部を変更した場合に全て のファイルを再コンパイルしなくても、変更のあったファイルだけをコンパイル し直せばよいということが頻繁に起きます。この処理を手作業で行うのはばかげ ています。基本的な考え方としては、ソースファイルの変更時刻をチェックし、 前回コンパイルした時より後に更新されたファイルだけをコンパイルして、全体 をリンクし直せば、必要な作業は完了するはずです。この作業を自動的に行うプ ログラムがmakeと呼ばれているものです。
実際は、それほど話は単純ではありません。例えば、いくつかのファイルが共
通して読み込んでいる
"header.h"
を更新した場合には、関連したファイルを再コンパイルする必要があります。こ
のように、どれを更新したら何を再処理しなければならない、というルールをあ
らかじめ記述しておき、それに従ってmakeが処理する、というのが一般的です。
このルールを記述するファイルは、歴史的にmakefile、あるいはMakefile、
MAKEFILEのような名前にすることが標準的です。
ところが、make自体はPDSやフリーソフトのものや、市販のコンパイラや処理系 に付属しているものなど、いろんな種類のものがあって、大筋ではかなり似てい るのですが、細部の機能は違っている、という困った状況になっています。例え ばBorland C++で処理できるmakefileをそのままMSCで使おうとしたら動かない、 というようなトラブルがしばしばありますから、注意が必要です。
char a[100][100][100]
を扱うために必要なメモリは、100×100×100バイトで、約0.95MBになります。
処理系によっては、コンパイル時や、あるいは処理系依存のグローバル変数を 指定することによって、スタックを大きさを変更することができます。ただし、 特にx86系のCPUの場合、上限が64KBに限られてしまうといったこともあるので、 油断はできません。
大きな配列を使うなら、それを
static
で宣言するか(おそらく、static変数はスタック以外の領域に配置されます)、あ
るいは
malloc
のようなメモリ獲得関数を使って、空きメモリの容量を監視しながら、メモリ不
足時に適切な処理を行うことを想定したコードを書く方が安全です。
#include <stdio.h> int main(void) { char s[14] = "hello, world!"; printf("%s¥n", s); return 0; }
配列を
static
で宣言するか、
strcpy
を使ってプログラムの中で明示的に初期化するように変更すれば、コンパイルで
きるようになるでしょう。