web-dev-qa-db-ja.com

Cで動的にscanfする最大文字列長を指定する(printfの "%* s"など)

この手法を使用して、scanfbufferに読み取る最大文字数を指定できます。

char buffer[64];

/* Read one line of text to buffer. */
scanf("%63[^\n]", buffer);

しかし、コードを書くときにバッファの長さがわからない場合はどうなるでしょうか。関数のパラメータの場合はどうなりますか?

void function(FILE *file, size_t n, char buffer[n])
{
    /* ... */
    fscanf(file, "%[^\n]", buffer); /* WHAT NOW? */
}

このコードはバッファオーバーフローに対して脆弱fscanfはバッファの大きさを知らないためです。

私は以前にこれを見たのを覚えていて、それが問題の解決策であると考え始めました:

fscanf(file, "%*[^\n]", n, buffer);

私が最初に考えたのは、*"%*[*^\n]"は、最大文字列サイズに引数(この場合はn)が渡されることを意味するということでした。これは、printf*の意味です。

ドキュメントでscanfを確認したところ、scanf[^\n]の結果を破棄する必要があることがわかりました。

scanfのバッファサイズを動的に渡すことができると非常に便利な機能になると思うので、これは少しがっかりしました。

バッファサイズをscanfに動的に渡す方法はありますか?

15
wefwefa3

基本的な答え

printf()scanf()形式指定子*に類似したものはありません。

プログラミング作法 では、KernighanとPikeはsnprintf()を使用してフォーマット文字列を作成することを推奨しています。

size_t sz = 64;
char format[32];
snprintf(format, sizeof(format), "%%%zus", sz);
if (scanf(format, buffer) != 1) { …oops… }

追加情報

例を完全な関数にアップグレードする:

int read_name(FILE *fp, char *buffer, size_t bufsiz)
{
    char format[16];
    snprintf(format, sizeof(format), "%%%zus", bufsiz - 1);
    return fscanf(fp, format, buffer);
}

これは、フォーマット仕様のサイズがバッファーのサイズより1つ小さいことを強調しています(これは、終了ヌルをカウントせずに保管できる非ヌル文字の数です)。これは、サイズ(int、ちなみにsize_tではない)がバッファのサイズであり、1つ小さいサイズであるfgets()とは対照的であることに注意してください。機能を向上させる方法は複数ありますが、そのポイントを示しています。 (必要に応じて、形式のs[^\n]に置き換えることができます。)

また、 TimČascomments に記載されているように、(残りの)入力行が必要な場合は、通常、fgets()を使用することをお勧めします。行を読み取るには、出力に改行が含まれることに注意してください(一方、%63[^\n]は、次のI/O操作で読み取られるように改行を残します)。より一般的なスキャン(たとえば、2つまたは3つの文字列)の場合、この手法の方が適している場合があります。特に、fgets()またはgetline()、次にsscanf()を使用して入力を解析する場合はそうです。

また、Microsoft(多かれ少なかれ)によって実装され、ISO/IEC 9899-2011(C11標準)のAnnex Kで標準化された、TR 24731-1の「安全な」機能には、明示的に長さが必要です。

if (scanf_s("%[^\n]", buffer, sizeof(buffer)) != 1)
    ...oops...

これにより、バッファオーバーフローが回避されますが、入力が長すぎるとエラーが発生する可能性があります。サイズは、以前のようにフォーマット文字列で指定できます/指定する必要があります。

if (scanf_s("%63[^\n]", buffer, sizeof(buffer)) != 1)
    ...oops...

if (scanf_s(format, buffer, sizeof(buffer)) != 1)
    ...oops...

生成されたフォーマット文字列を使用するコードでは、「非定数フォーマット文字列」に関する警告(一部のコンパイラからのフラグのセットの下)を無視または抑制する必要があることに注意してください。

12

実際、scanfファミリーの関数には可変幅指定子はありません。別の方法としては、フォーマット文字列を動的に作成する(幅がコンパイル時定数の場合、これは少しばかげているように見えますが)か、単にマジックナンバーを受け入れることが含まれます。 1つの可能性は、バッファとフォーマット文字列の幅の両方を指定するためにプリプロセッサマクロを使用することです。

#define STR_VALUE(x) STR(x)
#define STR(x) #x

#define MAX_LEN 63

char buffer[MAX_LEN + 1];
fscanf(file, "%" STR_VALUE(MAX_LEN) "[^\n]", buffer);
6
Arkku

別のオプションは、文字列の長さを#defineすることです。

#define STRING_MAX_LENGHT "%10s"

または

#define DOUBLE_LENGHT "%5d"
0
Patrick