嫌われ者のgets関数ですが、C11ではついに廃止になってしまいました。そんなgets関数ですが、とりあえずバッファオーバーランだけ防ぐことができれば、それなりに便利に使うことができます。そこで、今回はバッファオーバーランだけ対策し、使い勝手はまったく同じmy_gets関数を作ってみました。
0 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 |
#include <cstdio> inline void flockfile(FILE* stream) { ::_lock_file(stream); } inline void funlockfile(FILE* stream) { ::_unlock_file(stream); } inline int getc_unlocked(FILE* stream) { return _getc_nolock(stream); } inline int putc_unlocked(int c, FILE* stream) { return _putc_nolock(c, stream); } namespace { std::size_t my_gets_buffer_size = 256; } std::size_t get_my_gets_buffer_size() { return my_gets_buffer_size; } std::size_t set_my_gets_buffer_size(std::size_t n) { auto r = my_gets_buffer_size; if (n > 0) { flockfile(stdin); my_gets_buffer_size = n; funlockfile(stdin); } return r; } char* my_gets(char* s) { if (s == nullptr) return nullptr; flockfile(stdin); char* ss = s; while (!std::feof(stdin)) { int c = getc_unlocked(stdin); if (std::ferror(stdin)) return nullptr; if (c == '\n') break; if (ss - s < my_gets_buffer_size - 1) *ss++ = c; } funlockfile(stdin); *ss = '\0'; return s; } |
本文で解説するので、今回はあえて全くコメントを入れませんでした。3つ関数を定義しましたので順に解説していきますが、先に全体の設計思想を説明していきます。今回作ったmy_gets関数では、事前にバッファサイズを別の関数で設定しておきます。こうすることで、my_gets関数はgets関数と同じ型ですがバッファオーバーランを避けることができます。
まずはget_my_gets_buffer_size関数からです。この関数は現在設定されているバッファサイズを返します。デフォルトのバッファサイズは256です。
次はset_my_gets_buffer_size関数です。この関数では新しいバッファサイズを設定します。念のためflockfile/funlockfile関数で排他制御を行っています。引数nで新たなバッファサイズを指定しますが、0の場合は無意味な値になってしまいますので設定は変更しません。返却値として以前に設定されていたバッファサイズを返します。
いよいよgets関数に変わるmy_gets関数です。使い方はgets関数と全く同じです。バッファサイズの上限をチェックして、上限を超える文字は書き込まないようにしています。終端のナル文字は必ず格納しています。関数内部では、getc_unlocked関数を使ってflockfile/funlockfile関数で排他制御を行っています。
今回は排他制御にflockfile/funlockfile関数を使いました。Windows上のVisual C++やMinGWを使用する場合はこれらの関数は使えませんので、代わりに以下のように代替関数の定義を挿入しておくとよいでしょう。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
inline void flockfile(FILE* stream) { ::_lock_file(stream); } inline void funlockfile(FILE* stream) { ::_unlock_file(stream); } inline int getc_unlocked(FILE* stream) { return _getc_nolock(stream); } inline int putc_unlocked(int c, FILE* stream) { return _putc_nolock(c, stream); } |
これで一通りmy_gets関数とその関連関数ができました。バッファサイズは起動直後に一度だけ設定し、以後は頻繁に変えない方がよいでしょう。