为什么我不能使用strerror?
我正在将一些代码移植到Windows,而Microsoft编译器(Visual C ++ 8)告诉我strerror()
是不安全的。
抛开微软所有安全字符串中的烦恼因素,我实际上可以看到一些已弃用的函数是危险的。 但我无法理解strerror()
可能出现的问题。 它接受代码( int
),并返回相应的字符串,如果该代码未知,则返回空字符串。
危险在哪里?
在C中有一个很好的选择吗?
在C ++中有一个很好的选择吗?
[编辑]
得到了一些很好的答案,现在理解一些实现可能疯狂到实际写入一个共同的缓冲区 – 在单线程中不安全的重入,从不介意线程之间! – 我的问题不再是“为什么我不能使用它,有什么替代方案?” “C和/或C ++中是否有任何体面,简洁的替代品?”
提前致谢
不推荐使用strerror
,因为它不是线程安全的。 strerror
适用于内部静态缓冲区,可能被其他并发线程覆盖。 您应该使用名为strerror_s
的安全变体。
安全变体要求将缓冲区大小传递给函数,以便在写入缓冲区之前validation缓冲区是否足够大,从而有助于避免可能允许恶意代码执行的缓冲区溢出。
strerror
本身并不安全。 在线程之前的过去,它根本不是问题。 对于线程,两个或多个线程可以调用strerror
使返回的缓冲区处于未定义状态。 对于单线程程序,使用strerror
不应该受到伤害,除非他们在libc中玩一些奇怪的游戏,比如DLL中所有应用程序的常见内存。
为了解决这个问题,有一个相同function的新界面:
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()
返回的字符串,因为它可能会随着对函数的下一次调用而改变。 之前返回的值可能会过时。 特别是在multithreading环境中 ,您无法确保在访问字符串时该字符串是有效的。
想象一下:
Thread #1: char * error = strerror(1); Thread #2 char * error = strerror(2); printf(error);
根据strerror()
的实现,此代码打印出错误代码2的错误代码,而不是错误代码1。
对于简洁的包装器,您可以使用STLSoft的stlsoft::error_desc
,如下所示:
std::string errstr = stlsoft::error_desc(errno);
看一下代码,似乎它是用strerror()
,这意味着它在一个线程中可以安全地重入(即如果在给定的语句中多次使用),但是它没有解决multithreading问题。
它们似乎对缺陷运行非常快速的发布周期,所以你可以尝试请求mod吗?
虽然我不知道微软的原因,但我注意到strerror返回一个非const char *,这意味着有些风险是某些Merry Prankster在你做之前调用了strerror并修改了消息。
我理解其他答案,但我认为用代码显示更清楚。
检查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