为什么我不能使用strerror?

时间:2009-05-22 22:56:08

标签: c++ c deprecated

我正在将一些代码移植到Windows,而Microsoft编译器(Visual C ++ 8)告诉我strerror()不安全。

不考虑微软所有安全字符串中的烦恼因素,我实际上可以看到一些已弃用的函数是危险的。但我无法理解strerror()可能出现的问题。它需要一个代码(int),并返回相应的字符串,如果该代码未知,则返回空字符串。

危险在哪里?

C中有一个很好的选择吗?

C ++中有一个很好的选择吗?

[编辑]

得到了一些好的答案,现在明白某些实现可能足够疯狂到实际写入一个共同的缓冲区 - 单线程内的重入不安全,从不介意线程之间! - 我的问题不再是“为什么我不能使用它,有什么替代方案?” “C和/或C ++中是否有任何体面,简洁的替代品?”

提前致谢

7 个答案:

答案 0 :(得分:21)

不推荐使用

strerror,因为它不是线程安全的。 strerror适用于内部静态缓冲区,可能被其他并发线程覆盖。您应该使用名为strerror_s的安全变体。

安全变体要求将缓冲区大小传递给函数,以便在写入之前验证缓冲区是否足够大,从而有助于避免可能允许恶意代码执行的缓冲区溢出。

答案 1 :(得分:17)

strerror本身并不安全。在线程之前的过去,它根本不是问题。对于线程,两个或多个线程可以调用strerror,使返回的缓冲区处于未定义状态。对于单线程程序,使用strerror不应该受到伤害,除非他们在libc中玩一些奇怪的游戏,比如DLL中所有应用程序的公共内存。

为了解决这个问题,我们提供了一个相同功能的新界面:

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

请注意,调用者提供缓冲区空间和缓冲区大小。这解决了这个问题。即使对于单线程应用程序,您也可以使用它。它不会有点伤害,你也可以习惯这样做更安全。

注意:上面的原型是XSI规范。它可能因平台或编译器选项或#define符号而异。例如,GNU根据#define

提供或自己的版本

答案 2 :(得分:15)

  

得到了一些好的答案,现在明白某些实现可能足够疯狂到实际写入一个共同的缓冲区 - 单线程内的重入不安全,从不介意线程之间! - 我的问题不再是“为什么我不能使用它,有什么替代方案?” “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以外的错误代码,并且还显示一些调试信息。您可以根据自己的需要进行调整。

答案 3 :(得分:4)

您不能依赖strerror()返回的字符串,因为它可能会随着下次调用函数而改变。之前返回的值可能会过时。特别是在多线程环境中,您无法确保在访问该字符串时该字符串有效。

想象一下:

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

根据strerror()的实现,此代码打印出错误代码2的错误代码,而不是错误代码1。

答案 4 :(得分:1)

对于简洁的包装器,您可以使用STLSoftstlsoft::error_desc,如下所示:

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

查看代码,它似乎是用strerror()实现的,这意味着它在一个线程中可以安全地重入(即如果在给定语句中多次使用),但它不是解决多线程问题。

他们似乎为缺陷运行非常快速的发布周期,因此您可以尝试请求mod?

答案 5 :(得分:0)

虽然我不知道微软的原因,但我注意到strerror返回一个非const char *,这意味着有一些风险是某些Merry Prankster在你做之前调用了strerror并修改了这条消息。

答案 6 :(得分: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是golbal var。因此,再次致电strerror时,其内容可能会发生变化。这就是为什么它的线程不安全。

另一方面,strerror_r(int errnum, char *buf, size_t buflen),它会为参数buf生成错误字符串。所以现在没有全球资源。这就是为什么它是线程安全的。

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