[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
同じ質問が何度も繰り返される理由を考えること自体、興味深い問題ではあり ますが、実際にこの種の質問を見た限りでは、大半の疑問に対しては「疑問を持 つのももっともだ」と感じざるを得ないものがあります。C言語は多数のOS上で動 作しています。この結果、C言語は多くの人が使うようになりましたが、各処理系 によってふるまいが違うことも多くなるという欠点があり、それに対する疑問は C言語の仕様だけでは解決することができません。このことがFAQに拍車をかけて いるようです。例えば「画面をクリアするにはどうすればよいか」のような疑問 は、C言語の知識だけでは回答不能の、いわば、落とし穴のような問題だといえま す。しかし、「本質的にこのような問題はC言語とは無関係である」という理由が あるため、初心者向けの本にはあまり詳しいことは書かれていないようです。そ の結果、質問が何度も繰り返される、という悪循環が発生することになります。
というわけで、C言語をマスターするために必要な、しかしあまり本には書かれ ていない、この種のありきたりな質問を目についたものからピックアップしてみ よう、というのがこの企画です。回答は個人的な主観も混じりますので、異論も あるかもしれないし、常に解決方法が一つというわけでもありません。その場合 は「このような考え方もある」という程度に理解していただければ幸いです。
C使いへの道は、はてしなく遠いものです。他の言語に熟練している人ならよい のですが、プログラミングの経験がないと、最悪の場合は挫折することもありま す。市販コンパイラは手の届かない価格ではありませんが、買っても使わずに飾 るだけというのはもったいない話です。かといって、市販のソフトウェアを誰か にコピーしてもらったりするのは著作権法に違反するおそれがあります。
そこで、まずC言語とはどんなものか実際に試してみたいという段階なら、無料 で配布されているコンパイラがいくつかあるので、これを使うという手をおすす めします。
もしMS-DOSを使うという条件があれば、LSI C-86というコンパイラの試食版が 有名です。ネットにも登録されているので、ダウンロードすることもできますが、 データのサイズが大きいので結構時間がかかります。雑誌等に付録で付いている ものを探して使った方が便利だし安上がりになる場合があります。このプログラ ムは、条件を守った上でコピーすることが許されているので、既に持っている人 が身近にいれば、コピーしてもらっても構いません。
MS-DOSに関する知識に自信のある方なら、djgccを使う手があります。これは gcc(GNU C Compiler)をDOS上で実行できるようにしたものです。これを使う場合 は、go32というメモリ管理プログラムを併用する必要があります。C言語以外の 知識が必要という点では、チャレンジ精神が余計に必要となるでしょう。
MS-DOSにこだわらないで、かつ、必ず上達するのだという強い決意を持ってい るのであれば、フリーのUNIXを使う手をおすすめします。FreeBSDやLINUXには、 gccが添付されて配布されています。これはCだけでなくC++もコンパイルできるコ ンパイラです。ただし、一般論として、そもそもこれらのUNIXをインストールす るのが大変です。運が悪ければそんじょそこらの知識ではダメかもしれません。 パソコンの構成がたまたま標準的なものであれば、運良く何も考えなくても動作 することもあります。
なぜ、この方法をお薦めするかというと、これらのパッケージには大量のソー スコードが付いて来るからです。FreeBSDやLINUXそのものがCで書かれています。 中にはとんでもないプログラムもありますが、概してこのように配布されている ものは、ある程度のレベルに達しているものです。
経済的に余裕があれば、市販のコンパイラを買うという選択肢もあります。
市販コンパイラを選ぶ場合に注意すべきことが、いくつかあります。
--- * ---
・あなたの持っているマシンで動作するか。MS-DOSのバージョンが古すぎて…ということは、まさかないと思いますが、場 合によってはMS-DOSも新たに買う必要があります。Windowsがなければインストー ルできないコンパイラもあります。メモリが最低必要とされている以上になけれ ば動作しないし、環境によってはより多くのメモリが必要となります。これは製 品のカタログに書かれているので、購入前に確認してください。
・サポートはどうか。ユーザーサポートが有料化される傾向にあります。どのようにすれば、どの程 度のサポートが受けられるのか、ということを押さえておく必要があります。ユ ーザーサポートが有料だと良くない、というのは間違った発想です。問題はその 内容です。無料であっても役に立たないレベルのサポートは無意味だし、有料で もその価値のあるサポートなら元はちゃんと取れます。
・参考書はあるか。最近のコンパイラは、チュートリアルといって、C言語を勉強するためのテキス トと練習用のプログラムが付いていて、実際に例をコンパイルしながら身に付け ることができるように工夫されています。これを実際に行うのが上達の近道です が、どうしても分かりにくいという場合には、他の参考書が市販されていると便 利かもしれません。
・ネットで情報交換できるか。意外と便利なのがネットです。最新バージョンの情報、雑誌には載らないよう な細かいバグ情報や、ユーザーが実際に体験した「よくある勘違い」の情報、バ グ修正の差分データなど、ネットから迅速に入手することができるようになりま した。メーカーがSIG、フォーラム、ホームページ等を主催しているかどうかが ポイントになります。
・コンパイラの性能はどうか。コンパイラの性能としては、生成するコードの量、実行時の速度と、コンパイ ル自体にかかる速度が重要です。一般論としては、良いコードを出すものほどコ ンパイルに時間がかかる理屈になりますが、そう単純な話ではないはずです。
--- * ---さて、VC++とBorland C++を比較すれば、おそらく上記のような条件は五分五分 のはずです。従って難問になるわけです。
gccは基本的にUNIX上で動作しますが、数多くのCPU、そして処理系に移植され ています。DOS上で動作するものとしては、djgccと呼ばれるものが有名です。
gccは多くの資源、特にメモリを必要とします。最近のパソコンで、Windowsが 動作する程度のメモリがあるものなら動作は問題ありませんが、少し前にあった ような640KBのメモリ、80286以前のCPU、といった環境だと動作させることはでき ません。
gccは、世界中のフリーソフトウェア利用者から報告されたバグを修正したり、 また、新たな機能を追加したりして、徐々に進化している最中です。従って、バ ージョンアップは割と頻繁にあると考えてよいでしょう。現在のバージョンは、 2.6.3あたりだったと思います(1996年6月現在)。gccのバージョンは、このよう にピリオドで区切った3つの数字で表現し、左から右に向かって小さな変更によ るバージョン変更を意味することになっています。
GNUに関するタイムリーな情報は、インターネットのニュースが読めるならgnu というニュースグループを、そうでない場合にはUNIXに関連するニュースグルー プ、あるいは各パソコン通信のUNIX関連のsigが参考になります。
ただ、ネットでの意見を参考にした限りでは、どの本がいいというのはともか く、「この本だけは止めておけ」という本はあるようです。とはいえ、ここでそ れを書くのはちょっと気がすすまないので、というとかえって知りたいと思われ るかもしれませんが、とりあえず知りたい人はネットで誰かに聞いてみるか、今 までの議論のログを見てみるとよいと思います。私見としては、「これは止めて おけ」と言われた本は、忠告通り止めておいた方がいいと思います。
パソコン用に販売されているCコンパイラには、練習用のプログラムやマニュア ルが付いているものもあるので、それを使うのも一つの手です。
パソコン通信などにアクセスできる人なら、C言語に関するsig、フォーラム、 あるいはインターネットのニュースグループを見るのも勉強になります。
ただし、他のプログラミング言語の経験や、一般的なプログラミングに関する 知識、さらにはUNIX等のOSの知識が多少なければ、理解は困難かもしれません。 初心者の方は、この本を買って、読んだがさっぱり分からない、というのでも、 落ち込む必要は全くありません。
C言語の構成を大きく分類すると、二つに分けることができます。まず、言語 の仕様そのもの、すなわち、変数はどのように定義するとか、ループを実現する にはどんなキーワードを使うとか、どのような演算子があるか、ということがC 言語の仕様として決まっています。もう一つは、標準ライブラリと呼ばれている ものです。C言語は、関数という処理の単位を組み合わせることによってプログ ラムを作成します。プログラマーは必要な処理を、言語仕様に従って関数を作成 するという作業によって実現します。こうやって一から関数を作るのは大変な作 業になるので、多くのシステムで共通的に使われる処理は、最初から決められた 仕様の関数が用意されていて、これを標準ライブラリと呼んでいます。
C言語には、このように関数を後から追加することにより全体の処理を構築でき るという特徴があります。入出力の処理は、他の言語処理系では、言語仕様の中 に含まれることがありますが、例えば、C言語の場合は関数として実現されている ため、関数の中身を入れ替えるだけで他の処理系で動作できる可能性もあります。
しかし、システムによって実現の方法がまちまちな処理が多いという現実もあ ります。C言語は、このようなシステムに関連する処理を、言語自体としては関知 せず、処理系に専用の関数群をシステム毎に用意することによって解決していま す。いわば標準でないライブラリです。
このような処理は、C言語の仕様を超えた内容なので、質問に対してC言語の知 識だけでは回答することができません。回答するためには、その処理系に附属し ているライブラリの知識が必要になるのです。
(参考) Q&A(1)-8【処理系依存の問題】 ,Q&A(9)処理系依存の問題(全文)
これらの質問に回答するためには、どのような環境でそれを行うかという条件 が必要になります。
例えば、
printf("1") + printf("2");の結果は、
12かもしれないし、
21かもしれません。C言語の入門者は、足し算の左右どちらが先に実行されるか 分からないという事実に驚くかもしれませんが、実際、どちらが先に実行されて もC言語の仕様には違反しないのです。
動作が不定な場合も含め、基本的に処理系に依存するようなコードは書くべき ではありません。たまたま今あなたが使っている環境では思った通りに動くかも しれませんが、コンパイラをバージョンアップしてコンパイルしただけで、思っ た通りに動作しないことがあり得るからです。その場合にもコンパイラには一切 責任はありません。
ふるまいが不定であるようなコードは、たいてい、わずかな修正を行うだけで、 処理系に依存しない明確な処理に書き直すことができます。この場合、確実に12 と表示させたいなら、
printf("1"); printf("2");のように書けばよいのです。これが12という表示結果になることは、C言語で は保証されます。もちろん、次のように書いても期待通りの結果になります。
printf("12");
このように、多くの場合、動作が不定であるコードは、ほんの少しの修正で仕 様に定義されたコードに変更可能です。しかし、 エンディアン の問題のように、効率を無視しなければ簡単には解決できない場合もあります。
(参考) Q&A(7)-1【不定】
write_int(int i, FILE *fp) { fwrite(&i, sizeof(int), 1, fp); }このような書き方は、エンディアン(endian)とアライメント(alignment)に対し て問題を引き起こします。
エンディアンとは、マシン上で整数値を格納する時に、上位から格納するか下
位から格納するかということです。例えば0x1234という値をメモリ上に1バイトず
つ格納する場合に、0x12、0x34の順に1バイトずつ格納するマシンと、0x34、0x12
の順に格納するマシンがあります。前者をビッグエンディアン、後者をリトルエ
ンディアンと呼びます。
fwrite
は、メモリ上に保存されているデータのイメージをそのままファイルに書き出し
ます。ビッグエンディアンのマシンで作成したデータをリトルエンディアンで読
むと、0x1234と書いたつもりなのに、読んだ時には0x3412となってしまうでしょう。
アライメントの問題は、主に構造体のデータをそのまま
fwrite
を使って一度でファイルにしようとした時に発生します。構造体の多きさは、処
理系によって異なるかもしれないからです。
これらの問題を避けるためには、2バイト以上の大きさを持つデータは、数値を
アスキーで表現するか、あるいは
fwrite
を使わずに
putc
などを使って1バイトずつ確実な順番でファイルに書き込むのが確実です。
例えば
long
の変数
l
は次のように処理します。
putc((l >> 16) & 0xff, fp); putc((l >> 8) & 0xff, fp); putc((l & 0xff, fp);これは
fwrite
を使って一度に書くよりも、はるかに冗長なように見えますが、ディスクに書き
込むという処理は実際にディスクに書き込みをする時間の方が問題にならない位
長くなることが多いので、プログラムの実行、処理の時間としては、それほど致
命的にはなりません。
構造体のデータを書く時には、それぞれのフィールド毎に上のような手順で1バ
イトずつ書き込む関数を呼び出すとよいでしょう。
sizeof(STRUCT)
は、パディングを含んだサイズになるので、必ずしも各フィールドのサイズの合
計に等しいとは限らないことに気を付けてください。
int
のサイズの問題です。DOSでよく使われるCコンパイラでは、
int
のサイズは16ビットですが、UNIXなどのプログラムでは、
int
のサイズが32ビットであると想定してあるものがあります。この種のトラブルを
避けるには、
int
の変数を使う時には-32767〜32767の範囲に値が収まるように留意し、これを超え
るならば
long
の変数を使うようにするとよいでしょう。
#include <stdio.h> main(void) { printf("hello, world¥n"); return 0; }
strrev
という関数がみつからない。
strrev
という関数はANSI Cには定義されていません。ある関数が標準関数かどうかは、
K&R 2nd.を見るか、あるいはコンパイラのリファレンスマニュアルを見れば
判断できるはずです。
しかし、歴史的に割とよく使われる関数のようなので、必要なら、次の関数を どこかに追加して一緒にコンパイルしてください。
ただし、通常の用途において、文字列をひっくり返す必要は殆どないはずです。
strrev
を使う前に、本当に
strrev
が必要なのか、アルゴリズムを見直した方がよいと思います。元の文字列が意味
のある内容であれば、
strrev
した結果を最終的に使うためにはもう一度
strrev
に相当する処理を行う必要があります。配列やポインタを使えば文字列の内容は
そのままで、後ろから処理することはそう難しいことではないはずです。
なお、次の関数は2バイト文字には対応していません。また、一般に
strrev
という関数は文字列を単純に1バイト単位で入れ替えるだけのものです。
#include <string.h> /* reverse string * No Copyright */ char *strrev(char *s) { char *p; char *q; char tmp; if (s != NULL) { p = s; q = s + strlen(s) - 1; while (p > q) { tmp = *p; *p = *q; *q = tmp; p++; q--; } } return s; }