Changes between Version 19 and Version 20 of HowTo/CTutorial


Ignore:
Timestamp:
Sep 10, 2010, 8:45:15 AM (14 years ago)
Author:
村山 俊之
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • HowTo/CTutorial

    v19 v20  
    229229MinGW を導入する場合は、 [http://sourceforge.net/projects/mingw/files/ SourceForge.net の MinGW ダウンロードサイト]にて、「Download Now!」ボタンをクリック (または「Automated MinGW Installer」→「mingw-get-inst」→「mingw-get-inst-''yyyymmdd''」→「mingw-get-inst-''yyyymmdd''.exe」の順に選択) してインストーラをダウンロードし、実行して下さい。デフォルトの構成では C コンパイラのみの構成の GCC のみがインストールされます。 MinGW の GCC は Windows のコマンドプロンプトからでも扱うことはできますが、他の言語のコンパイラや UNIX ライクなターミナルが欲しい場合は、構成ツリーが表示されたところで該当するコンポーネントにチェックを入れて下さい。
    230230
     231MinGW を使用する場合は、さらに、 [http://gnuwin32.sourceforge.net/ GnuWin] プロジェクトが配布するパッケージ [http://gnuwin32.sourceforge.net/packages/libgw32c.htm LibGW32C] を入れておくことをお勧めします。これにより、 GNU 拡張仕様由来の一部の関数 (getline() など) が使えるようになります[[FootNote(使用する際には、コンパイル時にオプション -lgw32c を (末尾に!) つける必要があります (ex: gcc -o test test.c -lgw32c)。)]]。 LibGW32C の配布ページにて「Developer files」と書かれた方の Zip ファイルをダウンロードし、展開したファイルを、 MinGW のインストールディレクトリ下 (通常は C:\MinGW) に上書きコピーしてください。
     232
    231233 * Linux や *BSD、 Solaris、 Mac OS X などの UNIX ライクな OS を利用する場合
    232234
     
    398400と書くのと同じ意味で、文字列リテラルで char 型の配列を初期化した場合、末尾にヌル文字が挿入されるというルールになっています。
    399401
     402=== ユーザーに文字を入力させる ===
     403
     404次は、プログラムの実行中に、ユーザーにキーボードで文字を入力させる関数を見ていきましょう。これらも入出力ライブラリに含まれている関数です。
     405
     406{{{
     407#include <stdio.h>
     408
     409int main()
     410{
     411    char text[BUFSIZ];
     412    printf("何か入力してください。 >> ");
     413    gets(text);
     414    printf("あなたは今、「%s」と入力しましたね。\n", text);
     415    return 0;
     416}
     417}}}
     418
     419'''gets() 関数'''は、ユーザーに文字列を 1行だけ入力させる関数です。ユーザーがキーボードを用いて文字列を入力し、 Enter キー (または Return キー) を押して改行するまで、処理を返しません。入力した文字列は、 gets() 関数の引数に渡した char 型の配列に、ヌル文字で終端した文字列として格納されます。なお、ユーザーが Enter キーを押すことで、コンソール上では改行されますが、この改行は char 型の配列には含まれません。
     420
     421なお、上記で配列 text を宣言する際、添え字に "'''BUFSIZ'''" と書いていますが、これは入出力ライブラリにおいて定義されている定数値のマクロで、その内容は、そのプログラムを実行する環境において最適だと思われるバッファーのサイズです。つまり、コンソールから 1行を入力するんであれば、配列の大きさはこのぐらい確保しておくのが目安だよ、という値が入っています[[FootNote(本当に目安なので (多分 512 とか、そんな値)、あまり当てにできるものではありませんが…。)]]。
     422
     423…ここまで読んで、聡明な方であれば、「あれ? じゃあ BUFSIZ よりも大きいサイズの文字列を入力された場合、どうなるんだ?」と思うかも知れません。どうなるかというと、gets() 関数はそんな事情などお構いなしに、配列が存在する'''場所'''に、行全体を格納しようとします。その結果、本来配列として確保したわけではないメモリー領域にまで文字列が書き込まれてしまいます。つまり、'''メモリーが破壊されてしまいます'''。
     424
     425実は、 gets() 関数は最近の C 言語標準規格 (C89 や C99、 UNIX 関連だと POSIX.1-2001.LSB など) では非推奨とされており、 Linux の[http://www.linux.or.jp/JM/html/LDP_man-pages/man3/gets.3.html マニュアルページ]においては「'''絶対に使用してはならない'''」とさえ書かれている、代表的なセキュリティーホールの原因だったりします。 C 言語の世界ではこの手のメモリー管理に関する罠が少なくないので、注意が必要です。ちなみにさらに新しい UNIX 関連の規格 (POSIX.1-2008 など) では存在自体がなかったことにされていたりもするようなので、環境によっては上記のサンプルはコンパイルすら出来なかったかも知れません。
     426
     427{{{
     428#!c
     429#include <stdio.h>
     430#include <stdlib.h>
     431
     432int main()
     433{
     434    char *text = NULL;
     435    size_t length;
     436    ssize_t letter_num;
     437    printf("何か入力してください。 >> ");
     438    letter_num = getline(&text, &length, stdin);
     439    text[letter_num - 1] = '\0';    /* 改行文字を潰す */
     440    printf("あなたは今、「%s」と入力しましたね。\n", text);
     441    free(text); /* 動的に確保したメモリー領域を開放 */
     442    return 0;
     443}
     444}}}
     445
     446{{{
     447#!div class="important"
     448'''注意''': MinGW をお使いの方は、 GnuWin の LibGW32C ライブラリをインストールし、コンパイルコマンドの末尾に -lgw32c オプションを付けてコンパイルしてください。
     449
     450{{{
     451$ gcc -o test test.c -lgw32c
     452}}}
     453}}}
     454
     455そこで、使い物にならない gets() 関数の代用品として、 GNU は標準ライブラリに拡張仕様として '''getline() 関数'''を追加しました。この関数は、ファイルから 1行を入力し、その長さに応じて必要なだけのメモリー領域を動的に確保し、その領域を char 型の配列として取得する、というものです。
     456
     457{{{
     458    char *text = NULL;
     459}}}
     460
     461変数 text は char 型の'''ポインタ'''です。ポインタ変数とは、要するにメモリーアドレスを格納しておく変数のことですが、ここでは char 型の配列にアクセスする為の変数である、ぐらいに思っておいてください。 '''NULL''' は入出力ライブラリおよび一般ユーティリティライブラリ (stdlib.h) にて定義されている値で、メモリーアドレスとしては確実に無効である、という意味の定数です。絶対に無効であることが分かっていれば避けられる事故も多いので、多くの場面において利用される「意味のある」値です。なお、 getline() 関数で使用する場合、変数 text には必ず事前に NULL を入れておいてください。そうしないと、多分事故ります[[FootNote(NULL 以外のものを入れて使う方法もあるのですが、それはそれで微妙に特別な使い方だからです。ていうか、そんな細かいことまでここで書いてたら、多分キリがないです。orz)]]。
     462
     463{{{
     464    letter_num = getline(&text, &length, stdin);
     465}}}
     466
     467で、 getline() 関数を使うことによって、 text 変数から文字列が入った配列にアクセスできるようになります。ここで、変数 text の手前にアンパサント記号 "&" がついていますね。これは'''アドレス参照演算子'''といって、その変数に代入している値を実際に格納しているメモリーアドレスを取得しろ、というものです。こうすることによって、 getline() 関数は変数 text に、取得した文字列が格納された char 型の配列 (がある場所のアドレス値) を入れてあげることができる、というわけです。
     468
     469変数 length には、 getline() が動的に確保したメモリー領域のサイズが入ってきます。これまた、 & 記号が手前に付いているのは、 getline() 関数がこの変数に値を入れる為です。
     470
     471'''stdin''' は入出力ライブラリにおいてあらかじめ定義されている名前で、この名前を getline() 関数の 3つ目の引数に指定してあげると、 getline() 関数は文字列をファイルではなく、ユーザーによるキーボードでの入力から取得するようになります。
     472
     473getline() 関数の戻り値を letter_num 変数に代入していますね。ここには、 getline() 関数が取得した文字列の文字数が入ってきます。 length 変数と同じ値になるんじゃないの? と思いがちですが、 length 変数には文字列を格納する配列の末尾に追加するヌル文字 '\0' も含めた長さが入るので、 通常は letter_num == length - 1 になるはずです[[FootNote(getline() 関数が必要 _以上_ にメモリー領域を確保してはいけないという決まりはないので、 length にはさらに大きな値が入ってくる可能性もある。)]]。
     474
     475getline() 関数は gets() 関数とは違って、ユーザーが最後に押した Enter キーによる改行文字も捨てずに取得してきてしまいます。そこで、 getline() 関数の戻り値を代入した変数 letter_num の値を利用して改行文字の位置を特定し、それをヌル文字 '\0' に置き換えることで、次の printf() 関数が表示を行う際に、余計に改行しすぎないようにしています。それが以下の行ですね。
     476
     477{{{
     478    text[letter_num - 1] = '\0';    /* 改行文字を潰す */
     479}}}
     480
     481ところで、 C 言語はメモリーの使い方をプログラマー自身の責任で管理する必要のある言語です。 getline() 関数を用いる場合、 getline() 関数が動的に取得したメモリー領域を、後で開放してあげる必要があります。それをやってくれるのが、 '''free() 関数''' です。
     482
     483{{{
     484    free(text); /* 動的に確保したメモリー領域を開放 */
     485}}}
     486
     487この辺、 gets() 関数を使う場合に比べると若干面倒で知識も必要ですが、安全なプログラムを書く為には必要なコストだと割り切るほかないでしょう。
     488
     489
    400490[[FootNote]]