35

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

撇开微软提供的所有安全字符串中的烦恼因素不谈,我实际上可以看到一些已弃用的函数是危险的。但我不明白有什么问题strerror()。它接受一个代码 ( int),并返回相应的字符串,如果该代码未知,则返回空字符串。

危险在哪里?

C中有一个好的选择吗?

C++ 中有没有好的替代方案?

[编辑]

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

提前致谢

4

7 回答 7

31

strerror已弃用,因为它不是线程安全的。strerror在内部静态缓冲区上工作,该缓冲区可能会被其他并发线程覆盖。您应该使用一个名为strerror_s.

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

于 2009-05-22T23:02:43.433 回答
23

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

为了解决这个问题,有一个相同功能的新接口:

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

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

注意:上面的原型来自 POSIX 规范strerror_r()。它可能因平台或编译器选项或#define符号而异。例如,GNU 根据#define.

于 2009-05-22T23:27:21.433 回答
17

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

于 2009-05-23T10:59:52.463 回答
5

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

想象一下:

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

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

于 2009-05-22T23:03:36.693 回答
1

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

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

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

他们似乎对缺陷进行了相当快的发布周期,所以你可以尝试请求一个 mod 吗?

于 2009-05-22T23:29:50.790 回答
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不是已知错误时,它必须生成类似“Unknown error 41”的字符串。该字符串不是常量,而是生成到分配的缓冲区。并且buf是全局变量。strerror因此,当再次使用锁定调用时,其内容可能会发生变化。这就是为什么它是线程不安全的。

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

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

于 2018-04-12T12:46:34.870 回答
-1

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

于 2009-05-22T23:04:14.570 回答