| 402 | === ユーザーに文字を入力させる === |
| 403 | |
| 404 | 次は、プログラムの実行中に、ユーザーにキーボードで文字を入力させる関数を見ていきましょう。これらも入出力ライブラリに含まれている関数です。 |
| 405 | |
| 406 | {{{ |
| 407 | #include <stdio.h> |
| 408 | |
| 409 | int 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 | |
| 432 | int 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 | |
| 473 | getline() 関数の戻り値を letter_num 変数に代入していますね。ここには、 getline() 関数が取得した文字列の文字数が入ってきます。 length 変数と同じ値になるんじゃないの? と思いがちですが、 length 変数には文字列を格納する配列の末尾に追加するヌル文字 '\0' も含めた長さが入るので、 通常は letter_num == length - 1 になるはずです[[FootNote(getline() 関数が必要 _以上_ にメモリー領域を確保してはいけないという決まりはないので、 length にはさらに大きな値が入ってくる可能性もある。)]]。 |
| 474 | |
| 475 | getline() 関数は 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 | |