Почему я не могу использовать strerror?
Я портирую некоторый код на Windows, и компилятор Microsoft (Visual C++ 8) говорит мне, что strerror()
небезопасно
Оставляя в стороне фактор раздражения во всех безопасных вещах от Microsoft, я на самом деле вижу, что некоторые устаревшие функции опасны. Но я не могу понять, что может быть не так с strerror()
, Требуется код (int
) и возвращает соответствующую строку или пустую строку, если этот код неизвестен.
Где опасность?
Есть ли хорошая альтернатива в C?
Есть ли хорошая альтернатива в C++?
[редактировать]
Получив несколько хороших ответов и теперь понимая, что некоторые реализации могут быть достаточно сумасшедшими, чтобы фактически записывать в общий разделяемый буфер - небезопасно повторное включение в один поток, не говоря уже о потоках! - мой вопрос перестает быть "Почему я не могу использовать это, и каковы альтернативы?" "Есть ли достойные, лаконичные альтернативы в C и / или C++?"
заранее спасибо
8 ответов
strerror
устарела, потому что не является поточно-ориентированной. strerror
работает с внутренним статическим буфером, который может быть перезаписан другими параллельными потоками. Вы должны использовать безопасный вариант под названием strerror_s
,
Безопасный вариант требует, чтобы размер буфера передавался в функцию, чтобы проверить, достаточно ли велик размер буфера перед записью в него, что помогает избежать переполнения буфера, которое может позволить выполнить вредоносный код.
strerror
само по себе не небезопасно. В старые времена, прежде чем нарезать нить, это просто не было проблемой. С потоками два или более потоков могут вызывать strerror
оставив возвращенный буфер в неопределенном состоянии. Для однопоточных программ это не должно мешать использовать strerror
если они не играют в какие-то странные игры в libc, например, общую память для всех приложений в DLL.
Для решения этой проблемы есть новый интерфейс с той же функциональностью:
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()
этот код выводит код ошибки для кода ошибки 2, а не для кода ошибки 1.
Я понимаю другой ответ, но я думаю, что показ кода более понятен.
проверить реализацию 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
это golbal var. поэтому его содержание может измениться при вызове strerror
снова. Вот почему это небезопасно.
С другой стороны, strerror_r(int errnum, char *buf, size_t buflen)
, он генерирует строку ошибки для аргумента buf
, так что сейчас нет глобального ресурса. Вот почему это потокобезопасно.
ссылка: https://github.com/liuyang1/glibc/blob/master/string/strerror.c#L23-L26
Для краткой обертки вы можете использовать STLSoftstlsoft::error_desc
, как в:
std::string errstr = stlsoft::error_desc(errno);
Глядя на код, кажется, что он реализован с точки зрения strerror()
, это означает, что он будет безопасен для повторного входа в поток (т. е. если используется несколько раз в рамках данного оператора), но это не решает проблему многопоточности.
Похоже, что они работают с довольно быстрыми циклами выпуска дефектов, так что вы можете попробовать запросить мод?
Хотя я не знаю причин Microsoft, я отмечаю, что strerror возвращает неконстантный символ *, что означает, что существует риск того, что некоторые Merry Prankster вызвали strerror до того, как вы это сделали и изменили сообщение.
Спецификация этой функции как-то занижена в области потокобезопасности. Например. человеческая ошибка (3):
Эта строка не должна изменяться приложением, но может быть изменена последующим вызовом strerror().
Однако версии strerror для Windows API и GLIBC используют локальное хранилище потоков, поэтому их можно использовать совершенно безопасно.