gets
高木です。おはようございます。
昨日宣言したように、当面はマニアックなネタ、それも規格では非推奨、あるいは廃止されたものを蘇らせる方向で突き進みます。
今回は、CやC++ではその代表格ともいえるgets関数を蘇らせることにします。
gets関数というと、バッファオーバーランが検出できない危険な関数ということで知られています。
ところが、手軽に1行分の文字列を標準入力から読み込むには、これほど手軽な関数はありません。
fgets関数で代替するようにいわれても、そもそもの関数仕様が違うので、単純に置き換えただけではすみません。
C++には、std::stringを引数に取るstd::getline関数があります。
この関数ならgetsと同じぐらい手軽ですが、単純な配列に読み込みたい場合には大がかりすぎるのです。
だから、やはりgets関数なのです。
今回は、C++を使って、gets関数と同じように使えるにもかかわらず、バッファオーバーランその他の問題を解消する関数を再設計&実装してみます。
今回も単にインクルードするだけで使えるようにしています。
とりあえず、ヘッダーファイルにはgets関数と関連関数だけ実装していますがが、今後、増やしていけたらと考えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | // dradnats/cstdio #ifndef DRADNATS_CSTDIO_ #define DRADNATS_CSTDIO_ #pragma once #include <cstdio> namespace dradnats { #ifdef _WIN32 inline void flockfile(std::FILE* stream) { _lock_file(stream); } inline void funlockfile(std::FILE* stream) { _unlock_file(stream); } inline int getc_unlocked(std::FILE* stream) { return _getc_nolock(stream); } inline int putc_unlocked(int c, std::FILE* stream) { return _putc_nolock(c, stream); } #endif // 改良版gets // 格納可能な文字数を知らせるため、第1引数は必ず配列を指定しなければならない。 // (ポインタを渡すことはできない) // 第2引数に空ポインタ以外を指定したとき、改行文字を読み込めば*nlにtrueを、 // そうでない場合はfalseを設定する。 // 改行の有無にかかわらず、文字列の末尾には必ずナル文字を格納する。 template <std::size_t N> char* gets(char (&s)[N], bool* nl = 0) { #if __cplusplus < 201103L if (N == 0) // 保険 return 0; #else static_assert(N > 0, "N > 0"); #endif char* r = 0; bool nlf = false; flockfile(stdin); for (char* first = &s[0], * last = &s[N - 1]; first < last; ++first) { int c = getc_unlocked(stdin); if (c == EOF) goto end; if (c == '\n') { *first = '\0'; nlf = true; break; } *first = c; } if (!nlf) s[N - 1] = '\0'; if (nl != 0) *nl = nlf; r = &s[0]; end: funlockfile(stdin); return r; } } #endif // DRADNATS_CSTDIO_ // Copyright © 2017 by TAKAGI Nobuhisa // takagi@cloverfield.jp // どなたでも、自己の責任においてこのプログラムを使用していただいてかまいません。 |
今回の実装にあたって、flockfile関数、funlockfile関数、getc_unlocked関数が必要になりました。
互換性のために、Windows(Visual C++とMinGW-w64)向けにそれらの代替関数を実装しています。
LinuxやCygwinなどでは、処理系が持つ同名の関数を使っています。
今回の再設計では、getsの第1引数には配列型のオブジェクトしか渡せないようにしています。
格納可能な文字数を別途渡してもよかったのですが、getsと同じように使えることを優先しました。
とはいえ、別途サイズを渡せるようにしたほうが便利なこともあるので、それについては今後の課題とします。
それ以外に工夫した点として、オプションで第2引数を渡せるようにしました。
配列の要素数の範囲内で改行文字を読み取らなかったことを判定できるようにしています。
例外を送出することも考えたのですが、さすがにちょっとやり過ぎですからね。