web-dev-qa-db-ja.com

「strncpy」と「sprintf」

文字列をコピーするためにアプリケーションで使用されている多くのsprintfを見ることができます。

私は文字配列を持っています:

char myarray[10];
const char *str = "mystring";

文字列strmyarrayにコピーしたい場合は、次のように使用することをお勧めします。

sprintf(myarray, "%s", str);

または

strncpy(myarray, str, 8);

15
Vijay

どちらも使用すべきではありません。

  1. sprintfは危険であり、非推奨であり、snprintfに取って代わられています。文字列入力で古いsprintfを安全に使用する唯一の方法は、醜くてエラーが発生しやすいsprintfを呼び出す前に長さを測定するか、フィールド精度指定子(%.8sなど)を追加することですまたは%.*sとサイズ制限用の追加の整数引数)。これは、特に複数の%s指定子が関係している場合は、醜くてエラーが発生しやすくなります。

  2. strncpyも危険です。 notバッファサイズが制限されたバージョンのstrcpyです。これは、文字を固定長のnull -padded(null -terminatedではなく)配列にコピーするための関数です。ここで、ソースはC文字列または少なくとも宛先のサイズの固定長文字配列。その使用目的は、固定サイズのテキストフィールドで機能し、ヌル終了のためにディスクまたはメモリ内の1バイトも無駄にしたくない、レガシーUNIXディレクトリテーブル、データベースエントリなどです。 canバッファサイズが制限されたstrcpyとして誤用されますが、そうすることは2つの理由で有害です。まず、バッファ全体が文字列データに使用されている場合(つまり、ソース文字列の長さが少なくともdestバッファと同じ長さである場合)、null終了に失敗します。自分で終了を追加することもできますが、これは醜く、エラーが発生しやすくなります。次に、ソース文字列が出力バッファよりも短い場合、strncpyは常に宛先バッファ全体にnullバイトを埋め込みます。これは単に時間の無駄です。

では、代わりに何を使用する必要がありますか?

BSD strlcpy関数が好きな人もいます。意味的には、戻り値がsize_tであり、文字列の長さに人為的なINT_MAX制限を課さないことを除いて、snprintf(dest, destsize, "%s", source)と同じです。ただし、ほとんどの一般的な非BSDシステムにはstrlcpyがなく、独自のシステムを作成すると危険なエラーが発生しやすいため、使用する場合は、信頼できるソースから安全で動作するバージョンを入手する必要があります。

私の好みは、重要な文字列構造にはsnprintfを使用し、パフォーマンスが重要であると測定されたいくつかの重要なケースにはstrlen + memcpyを使用することです。このイディオムを正しく使用する習慣をつけると、文字列関連の脆弱性を含むコードを誤って作成することがほぼ不可能になります。

39
R..

Printf/scanfのさまざまなバージョンは、次の理由により、非常に遅い関数です。

  • それらは可変引数リストを使用するため、パラメーターの受け渡しがより複雑になります。これは、さまざまなあいまいなマクロとポインタを介して行われます。すべての引数は、そのタイプを判別するために実行時に解析される必要があり、追加のオーバーヘッドコードが追加されます。 (VAリストも言語の非常に冗長な機能であり、単純なパラメーターの受け渡しよりも入力がはるかに弱いため、危険でもあります。)

  • 多くの複雑なフォーマットとサポートされているすべての異なるタイプを処理する必要があります。これにより、関数にも多くのオーバーヘッドが追加されます。すべての型の評価は実行時に行われるため、コンパイラーは使用されない関数の部分を最適化することはできません。したがって、printf()を使用して整数のみを出力したい場合は、スペースの完全な浪費として、プログラムにリンクされた浮動小数点数、複素数演算、文字列処理などのサポートが得られます。

  • 一方、strcpy()、特にmemcpy()のような関数は、コンパイラーによって大幅に最適化されており、最大のパフォーマンスを得るためにインラインアセンブラで実装されることがよくあります。

ベアボーン16ビットローエンドマイクロコントローラーでかつて行った測定の一部を以下に示します。

経験則として、いかなる形式のプロダクションコードでもstdio.hを使用しないでください。これは、デバッグ/テストライブラリと見なされます。 MISRA-C:2004では、製品コードでstdio.hを禁止しています。

[〜#〜]編集[〜#〜]

主観的な数値を事実に置き換えました:

ターゲットFreescaleHCS12、コンパイラFreescale Codewarrior5.1でのstrcpyとsprintfの測定。 sprintfのC90実装を使用すると、C99はまだ効果がありません。すべての最適化が有効になっています。次のコードがテストされました。

  const char str[] = "Hello, world";
  char buf[100];

  strcpy(buf, str);
  sprintf(buf, "%s", str);

コールスタックのオン/オフのパラメータシャッフルを含む実行時間:

strcpy   43 instructions
sprintf  467 instructions

割り当てられたプログラム/ ROMスペース:

strcpy   56 bytes
sprintf  1488 bytes

割り当てられたRAM /スタックスペース:

strcpy   0 bytes
sprintf  15 bytes

内部関数呼び出しの数:

strcpy   0
sprintf  9

関数呼び出しスタックの深さ:

strcpy   0 (inlined)
sprintf  3 
3
Lundin

文字列をコピーするためだけにsprintfを使用することはありません。それはやり過ぎであり、そのコードを読んだ人は確かに立ち止まって、なぜ私がそれをしたのか、そして彼ら(または私)が何かを見逃していないのか疑問に思うでしょう。

Sprintf()(または妄想的である場合はsnprintf())を使用して「安全な」文字列コピーを実行する方法が1つあります。これは、フィールドをオーバーフローしたり、NULで終了しないままにする代わりに切り捨てます。

つまり、次のように「*」フォーマット文字を「文字列精度」として使用します。

そう:

char dest_buff[32];
....
sprintf(dest_buff, "%.*s", sizeof(dest_buff) - 1, unknown_string);

これにより、unknown_stringの内容がdest_buffに配置され、終了するNUL用のスペースが確保されます。

0
MikeW