Почему я не могу использовать strerror? - PullRequest
28 голосов
/ 23 мая 2009

Я портирую некоторый код на Windows, и компилятор Microsoft (Visual C ++ 8) сообщает мне, что strerror() небезопасен.

Если отбросить фактор раздражения во всех безопасных вещах от Microsoft, я могу фактически увидеть, что некоторые устаревшие функции опасны. Но я не могу понять, что может быть не так с strerror(). Он принимает код (int) и возвращает соответствующую строку или пустую строку, если этот код неизвестен.

Где опасность?

Есть ли хорошая альтернатива в C?

Есть ли хорошая альтернатива в C ++?

[править]

Имея несколько хороших ответов и теперь понимая, что некоторые реализации могут быть достаточно сумасшедшими, чтобы фактически записывать в общий разделяемый буфер - небезопасно входить в один поток, не говоря уже о потоках! - мой вопрос перестает быть «Почему я не могу его использовать, и каковы альтернативы?» «Есть ли достойные, лаконичные альтернативы в C и / или C ++?»

Заранее спасибо

Ответы [ 7 ]

21 голосов
/ 23 мая 2009

strerror устарело, потому что оно не поточно-ориентированное. strerror работает с внутренним статическим буфером, который может быть перезаписан другими параллельными потоками. Вы должны использовать безопасный вариант под названием strerror_s.

Безопасный вариант требует, чтобы размер буфера передавался в функцию для проверки того, что буфер достаточно велик перед записью в него, что помогает избежать переполнения буфера, которое может позволить выполнить вредоносный код.

17 голосов
/ 23 мая 2009

strerror само по себе не небезопасно. В старые времена, прежде чем нарезать нить, это просто не было проблемой. С потоками два или более потоков могут вызывать strerror, оставляя возвращенный буфер в неопределенном состоянии. Для однопоточных программ не помешает использовать strerror, если они не играют в какие-то странные игры в libc, например, общую память для всех приложений в DLL.

Для решения этой проблемы есть новый интерфейс с той же функциональностью:

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

Обратите внимание, что вызывающая сторона предоставляет пространство буфера и размер буфера. Это решает проблему. Даже для однопоточных приложений вы можете использовать его. Это не повредит немного, и вы могли бы также привыкнуть делать это более безопасным способом.

ПРИМЕЧАНИЕ: приведенный выше прототип является спецификацией XSI. Это может варьироваться в зависимости от платформы или с параметрами компилятора или #define символов. Например, GNU делает эту или свою собственную версию доступной в зависимости от #define

15 голосов
/ 23 мая 2009

Имея несколько хороших ответов и теперь понимая, что некоторые реализации могут быть достаточно сумасшедшими, чтобы фактически записывать в общий разделяемый буфер - небезопасно входить в один поток, не говоря уже о потоках! - мой вопрос перестает быть «Почему я не могу его использовать, и каковы альтернативы?» «Есть ли достойные, лаконичные альтернативы в 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, а также отображает некоторую информацию об отладке. Вы можете адаптировать его под свои нужды.

4 голосов
/ 23 мая 2009

Нельзя полагаться на строку, возвращаемую strerror(), поскольку она может измениться при следующем вызове функции. Ранее возвращенные значения могут устареть. Особенно в многопоточных средах , вы не можете гарантировать, что строка действительна при обращении к ней.

Представьте себе это:

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

В зависимости от реализации strerror() этот код выводит код ошибки для кода ошибки 2, а не для кода ошибки 1.

1 голос
/ 23 мая 2009

Для краткой обертки вы можете использовать STLSoft stlsoft::error_desc, например:

std::string errstr = stlsoft::error_desc(errno);

Глядя на код, кажется, что он реализован в терминах strerror(), что означает, что он будет безопасен для повторного входа в поток (т. Е. Если используется несколько раз в данном выражении), но он не затрагивает проблема многопоточности.

Кажется, что они работают довольно быстро для исправления дефектов, так что вы можете попробовать запросить мод?

0 голосов
/ 12 апреля 2018

Я понимаю другой ответ, но я думаю, что показ кода более понятен.

проверка реализации 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

0 голосов
/ 23 мая 2009

Хотя я не знаю причин Microsoft, отмечу, что strerror возвращает неконстантный символ *, что означает, что существует риск того, что какой-то Merry Prankster вызвал strerror до того, как вы это сделали и изменили сообщение.

...