关于c ++:为什么我不能使用strerror?

Why can't I use strerror?

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

除了Microsoft的所有安全字符串中的烦人因素之外,我实际上可以看到某些不推荐使用的函数很危险。 但是我不明白strerror()可能有什么问题。 它接受一个代码(int),并返回相应的字符串,如果该代码未知,则返回空字符串。

危险在哪里?

C语言中有很好的替代方法吗?

C ++中有一个很好的替代方法吗?

[编辑]

有了一些不错的答案,并且现在了解到某些实现可能疯狂到实际上无法写入公共共享缓冲区-在单线程内重入是不安全的,不必在线程之间进行操作! -我的问题不再是"我为什么不能使用它,还有其他选择?" 到"在C和/或C ++中是否有任何体面,简洁的选择?"

提前致谢


不推荐使用strerror,因为它不是线程安全的。 strerror在内部静态缓冲区上工作,该缓冲区可能被其他并发线程覆盖。您应该使用称为strerror_s的安全变体。

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


strerror本身并不是不安全的。在以前的线程时代,这根本不是问题。对于线程,两个或多个线程可以调用strerror,使返回的缓冲区处于未定义状态。对于单线程程序,除非使用strerror,除非它们正在libc中玩一些怪异的游戏(例如DLL中所有应用程序的公用内存),否则应该不会感到受伤。

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

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

请注意,调用方提供了缓冲区空间和缓冲区大小。这样就解决了问题。即使对于单线程应用程序,您也可以使用它。它不会造成任何伤害,您也可能会习惯以更安全的方式进行操作。

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


Having had some good answers, and now understanding that some implementations may be crazy enough to actually write to a common shared buffer - unsafe to reentrancy within a single-thread, never mind between threads! - my question stops being"Why can't I use it, and what are the alternatives?" to"Are there any decent, succinct alternatives in C and/or C++?"

Posix指定strerror_r(),并且在Windows上可以使用strerror_s(),这有点不同,但目标相同。我这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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)
"
,
          usr_msg, sys_msg, filename, line_number);
}

我编写此函数是因为Posix线程函数不会修改errno,而是返回错误代码。因此,此功能与perror()基本上相同,不同之处在于它允许您提供除errno以外的错误代码,并显示一些调试信息。您可以根据需要进行调整。


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

想象一下:

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

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


对于简洁的包装,可以使用STLSoft的stlsoft::error_desc,如下所示:

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

查看代码,似乎是用strerror()来实现的,这意味着它对于线程内的重入将是安全的(即如果在给定的语句中多次使用),但是并不能解决多线程问题。

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


我知道其他答案,但我认为使用代码显示会更清晰。

检查glibc的实现(我们应该在MS lib中获得类似的代码)

1
2
3
4
/* 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


尽管我不知道Microsoft的原因,但我注意到strerror返回一个非const char *,这意味着存在某些Merry Prankster在您更改和修改消息之前调用strerror的风险。