一部のコードをWindowsに移植していますが、Microsoftコンパイラ(Visual C++ 8)から、strerror()
は安全ではないと言われています。
Microsoftのすべての安全な文字列に含まれる煩わしさは別として、非推奨の関数のいくつかは危険であることが実際にわかります。しかし、strerror()
の何が問題なのか理解できません。コード(int
)を受け取り、対応する文字列を返します。コードが不明な場合は空の文字列を返します。
危険はどこにありますか?
Cに良い代替手段はありますか?
C++に良い代替手段はありますか?
[編集]
いくつかの良い答えがあり、いくつかの実装は実際に共通の共有バッファに書き込むのに十分クレイジーである可能性があることを理解しました-単一スレッド内での再入可能性には安全ではありません。スレッド間で気にしないでください! -私の質問は、「なぜそれを使用できないのか、そして代替手段は何ですか?」ではなくなります。 「Cおよび/またはC++にまともで簡潔な代替手段はありますか?」
前もって感謝します
strerror
はスレッドセーフではないため、非推奨です。 strerror
は、他の並行スレッドによって上書きされる可能性のある内部静的バッファーで機能します。 strerror_s
と呼ばれる安全なバリアントを使用する必要があります。
セキュアバリアントでは、バッファに書き込む前にバッファが十分に大きいことを検証するために、バッファサイズを関数に渡す必要があります。これにより、悪意のあるコードの実行を可能にするバッファオーバーランを回避できます。
strerror
自体は安全ではありません。スレッド化する前の昔は、それは単に問題ではありませんでした。スレッドの場合、2つ以上のスレッドがstrerror
を呼び出して、返されたバッファを未定義の状態のままにする可能性があります。シングルスレッドプログラムの場合、DLL内のすべてのアプリの共通メモリなど、libcで奇妙なゲームをプレイしていない限り、strerror
を使用しても問題はありません。
これに対処するために、同じ機能への新しいインターフェースがあります。
int strerror_r(int errnum, char *buf, size_t buflen);
呼び出し元がバッファースペースとバッファーサイズを提供することに注意してください。これで問題は解決します。シングルスレッドアプリケーションの場合でも、それを使用することをお勧めします。それは少し傷つくことはありません、そしてあなたはそれをより安全な方法で行うことに慣れたほうがよいでしょう。
注:上記のプロトタイプはXSI仕様です。プラットフォームごとに、またはコンパイラオプションや#define
記号によって異なる場合があります。たとえば、GNUは、#define
に応じて、そのバージョンまたは独自のバージョンを利用できるようにします。
いくつかの良い答えがあり、いくつかの実装は実際に共通の共有バッファに書き込むのに十分クレイジーである可能性があることを理解しました-単一スレッド内での再入可能性には安全ではありません。スレッド間で気にしないでください! -私の質問は、「なぜそれを使用できないのか、そして代替手段は何ですか?」ではなくなります。 「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
以外のエラーコードを提供でき、デバッグ情報も表示される点が異なります。あなたはそれをあなたの必要に適応させることができます。
strerror()
によって返される文字列は、次の関数の呼び出しで変更される可能性があるため、信頼できません。以前に返された値は、その後廃止される可能性があります。特にマルチスレッド環境では、アクセス時に文字列が有効であることを保証できません。
これを想像してみてください:
_Thread #1:
char * error = strerror(1);
Thread #2
char * error = strerror(2);
printf(error);
_
strerror()
の実装に応じて、このコードはエラーコード1ではなくエラーコード2のエラーコードを出力します。
簡潔なラッパーの場合、次のように STLSoft の_stlsoft::error_desc
_を使用できます。
_std::string errstr = stlsoft::error_desc(errno);
_
コードを見ると、strerror()
の観点から実装されているようです。つまり、スレッド内での再入可能性は安全です(つまり、特定のステートメント内で複数回使用された場合)が、マルチスレッドの問題。
彼らは欠陥のためにかなり速いリリースサイクルを操作しているようです、それであなたはmodを要求することを試みることができますか?
Microsoftの理由はわかりませんが、strerrorが非const char *を返すことに注意してください。これは、メッセージを変更して変更する前に、一部のMerryPranksterがstrerrorを呼び出したリスクがあることを意味します。
他の答えは理解できますが、コードで表示する方が明確だと思います。
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