web-dev-qa-db-ja.com

なぜstrerrorを使用できないのですか?

一部のコードをWindowsに移植していますが、Microsoftコンパイラ(Visual C++ 8)から、strerror()は安全ではないと言われています。

Microsoftのすべての安全な文字列に含まれる煩わしさは別として、非推奨の関数のいくつかは危険であることが実際にわかります。しかし、strerror()の何が問題なのか理解できません。コード(int)を受け取り、対応する文字列を返します。コードが不明な場合は空の文字列を返します。

危険はどこにありますか?

Cに良い代替手段はありますか?

C++に良い代替手段はありますか?

[編集]

いくつかの良い答えがあり、いくつかの実装は実際に共通の共有バッファに書き込むのに十分クレイジーである可能性があることを理解しました-単一スレッド内での再入可能性には安全ではありません。スレッド間で気にしないでください! -私の質問は、「なぜそれを使用できないのか、そして代替手段は何ですか?」ではなくなります。 「Cおよび/またはC++にまともで簡潔な代替手段はありますか?」

前もって感謝します

28
JamieH

strerrorはスレッドセーフではないため、非推奨です。 strerrorは、他の並行スレッドによって上書きされる可能性のある内部静的バッファーで機能します。 strerror_sと呼ばれる安全なバリアントを使用する必要があります。

セキュアバリアントでは、バッファに書き込む前にバッファが十分に大きいことを検証するために、バッファサイズを関数に渡す必要があります。これにより、悪意のあるコードの実行を可能にするバッファオーバーランを回避できます。

21
dfa

strerror自体は安全ではありません。スレッド化する前の昔は、それは単に問題ではありませんでした。スレッドの場合、2つ以上のスレッドがstrerrorを呼び出して、返されたバッファを未定義の状態のままにする可能性があります。シングルスレッドプログラムの場合、DLL内のすべてのアプリの共通メモリなど、libcで奇妙なゲームをプレイしていない限り、strerrorを使用しても問題はありません。

これに対処するために、同じ機能への新しいインターフェースがあります。

int strerror_r(int errnum, char *buf, size_t buflen);

呼び出し元がバッファースペースとバッファーサイズを提供することに注意してください。これで問題は解決します。シングルスレッドアプリケーションの場合でも、それを使用することをお勧めします。それは少し傷つくことはありません、そしてあなたはそれをより安全な方法で行うことに慣れたほうがよいでしょう。

注:上記のプロトタイプはXSI仕様です。プラットフォームごとに、またはコンパイラオプションや#define記号によって異なる場合があります。たとえば、GNUは、#defineに応じて、そのバージョンまたは独自のバージョンを利用できるようにします。

17
dwc

いくつかの良い答えがあり、いくつかの実装は実際に共通の共有バッファに書き込むのに十分クレイジーである可能性があることを理解しました-単一スレッド内での再入可能性には安全ではありません。スレッド間で気にしないでください! -私の質問は、「なぜそれを使用できないのか、そして代替手段は何ですか?」ではなくなります。 「Cおよび/またはC++にまともで簡潔な代替手段はありますか?」

Posixはstrerror_r()を指定し、Windowsではstrerror_s()を使用できます。これは少し異なりますが、同じ目的を持っています。私はこれをします:

_#define BAS_PERROR(msg, err_code)\
  bas_perror(msg, err_code, __FILE__, __LINE__)

void bas_perror (const char* msg, int err_code, const char* filename,
                 unsigned long line_number);


void
bas_perror (const char* usr_msg, int err_code, const char* filename,
            unsigned long line_number)
{
  char sys_msg[64];

#ifdef _WIN32
  if ( strerror_s(sys_msg, sizeof sys_msg, err_code) != 0 )
  {
    strncpy(sys_msg, "Unknown error", taille);
    sys_msg[sizeof sys_msg - 1] = '\0';
  }
#else
  if ( strerror_r(err_code, sys_msg, sizeof sys_msg) != 0 )
  {
    strncpy(sys_msg, "Unknown error", sizeof sys_msg);
    sys_msg[sizeof sys_msg - 1] = '\0';
  }
#endif

  fprintf(stderr, "%s: %s (debug information: file %s, at line %lu)\n",
          usr_msg, sys_msg, filename, line_number);
}
_

Posixスレッド関数はerrnoを変更せず、代わりにエラーコードを返すため、この関数を作成しました。したがって、この関数は基本的にperror()と同じですが、errno以外のエラーコードを提供でき、デバッグ情報も表示される点が異なります。あなたはそれをあなたの必要に適応させることができます。

15

strerror()によって返される文字列は、次の関数の呼び出しで変更される可能性があるため、信頼できません。以前に返された値は、その後廃止される可能性があります。特にマルチスレッド環境では、アクセス時に文字列が有効であることを保証できません。

これを想像してみてください:

_Thread #1:
char * error = strerror(1);
                                    Thread #2
                                    char * error = strerror(2);
printf(error);
_

strerror()の実装に応じて、このコードはエラーコード1ではなくエラーコード2のエラーコードを出力します。

4
beef2k

簡潔なラッパーの場合、次のように STLSoft の_stlsoft::error_desc_を使用できます。

_std::string errstr = stlsoft::error_desc(errno);
_

コードを見ると、strerror()の観点から実装されているようです。つまり、スレッド内での再入可能性は安全です(つまり、特定のステートメント内で複数回使用された場合)が、マルチスレッドの問題。

彼らは欠陥のためにかなり速いリリースサイクルを操作しているようです、それであなたはmodを要求することを試みることができますか?

1
dcw

Microsoftの理由はわかりませんが、strerrorが非const char *を返すことに注意してください。これは、メッセージを変更して変更する前に、一部のMerryPranksterがstrerrorを呼び出したリスクがあることを意味します。

0

他の答えは理解できますが、コードで表示する方が明確だと思います。

glibcの実装を確認してください(MS libで同様のコードを取得する必要があります)

_/* Return a string describing the errno code in ERRNUM.
   The storage is good only until the next call to strerror.
   Writing to the storage causes undefined behavior.  */
libc_freeres_ptr (static char *buf);
_

errnumが既知のエラーではない場合、「不明なエラー41」のような文字列を生成する必要があります。この文字列は定数ではありませんが、割り当てられたバッファに生成されます。そして、bufはgolbalvarです。そのため、strerrorを再度呼び出すと内容が変わる可能性があります。それがスレッドセーフである理由です。

一方、strerror_r(int errnum, char *buf, size_t buflen)は、引数bufへのエラー文字列を生成します。したがって、現在、グローバルリソースはありません。それがスレッドセーフである理由です。

参照: https://github.com/liuyang1/glibc/blob/master/string/strerror.c#L23-L26

0
liuyang1