我正在将一些代码移植到Windows,Microsoft编译器(Visual C ++ 8)告诉我strerror()是不安全的。
除了Microsoft的所有安全字符串中的烦人因素之外,我实际上可以看到某些不推荐使用的函数很危险。 但是我不明白strerror()可能有什么问题。 它接受一个代码(int),并返回相应的字符串,如果该代码未知,则返回空字符串。
危险在哪里?
C语言中有很好的替代方法吗?
C ++中有一个很好的替代方法吗?
[编辑]
有了一些不错的答案,并且现在了解到某些实现可能疯狂到实际上无法写入公共共享缓冲区-在单线程内重入是不安全的,不必在线程之间进行操作! -我的问题不再是"我为什么不能使用它,还有其他选择?" 到"在C和/或C ++中是否有任何体面,简洁的选择?"
提前致谢
-
您不能使用它,因为Microsoft说"该死的ISO C标准-我们将不允许您使用它,除非您用实用指示覆盖警告或错误"。 他们还禁止了memcpy()-这很荒谬,因为您告诉它要复制多少字节,并且如果您不能考虑它,并且在调用memcpy()之前知道目标空间中有足够的空间容纳字节数,那么您就不属于 在一个团队中用C或C ++编写代码。
不推荐使用strerror,因为它不是线程安全的。 strerror在内部静态缓冲区上工作,该缓冲区可能被其他并发线程覆盖。您应该使用称为strerror_s的安全变体。
安全变体要求将缓冲区大小传递给函数,以便在写入缓冲区之前验证缓冲区是否足够大,从而有助于避免可能导致恶意代码执行的缓冲区溢出。
-
嗯,问题是:为什么它被认为不安全?你完全没告诉我。
-
他确实告诉过你为什么。它在内部静态缓冲区上工作-即在线程之间共享的缓冲区。那不安全。
-
是的。我很抱歉。现在,对整个世界来说,下一个问题是:为什么有人会像那样实现它呢?
-
JamieH,可以将该函数有效地实现为char * msg [] = {"未找到文件","您生病了","您不是root","时间结束了"); char * strerror(int n){return msg [n]; }
-
litb-我想这会是这样(尽管有些复杂,可以将代码与字符串匹配)Andrew C-大声笑!
-
@litb和@JamieH:最简单的实现仅比大纲稍微复杂一点。语言环境带来了一个问题-如果消息需要本地化,等等。
-
关于语言环境的所有信息-从strerror返回的字符串需要保持有效,直到再次调用strerror为止,即使您更改了语言环境(这可能会卸载旧的语言环境)。因此,将指针返回到语言环境数据意味着实现需要保留旧的语言环境。我认为它是可行的,但并不像您想的那么简单。
-
确实,我只发布了非常粗略的计划。显然,它在RealLife中更加复杂:)
-
我以为strerror()可以追溯到Unix有线程之前
-
它实际上很难使它成为线程安全的1.没有互斥体2。没有过多地复制内存,并且3.知道了区域设置。选择任何2和它的容易。
-
@Erik,在我看来,让strerror表保持加载状态对于使用线程安全的strerror()来说代价不菲。我可以理解在1995年完全卸载表的决定,但是我不确定为什么为什么2019 Windows Windows需要一个千兆字节的内存基线,而该Windows不能容纳最多几千个本地语言环境字符串,可能是由共享的内存支持的所有程序。程序多久卸载一次语言环境?"丢失"的存储潜力似乎很小。
strerror本身并不是不安全的。在以前的线程时代,这根本不是问题。对于线程,两个或多个线程可以调用strerror,使返回的缓冲区处于未定义状态。对于单线程程序,除非使用strerror,除非它们正在libc中玩一些怪异的游戏(例如DLL中所有应用程序的公用内存),否则应该不会感到受伤。
为了解决这个问题,有一个相同功能的新接口:
1
| int strerror_r(int errnum, char *buf, size_t buflen); |
请注意,调用方提供了缓冲区空间和缓冲区大小。这样就解决了问题。即使对于单线程应用程序,您也可以使用它。它不会造成任何伤害,您也可能会习惯以更安全的方式进行操作。
注意:上面的原型是XSI规范。它可能因平台或编译器选项或#define符号而异。例如,GNU根据#define使该版本或它们自己的版本可用
-
还有TR24731,它定义了strerror_s()。
-
"在以前的线程时代,这根本不是问题"。它也不是可重入的,但是从不声称它可以从信号处理程序中调用。
-
没问题,只是信号处理程序中的longjmp;)
-
请注意,没有安全大小可用于预分配strerror_r输出缓冲区。 从技术上讲正确的程序必须使用malloc循环,直到该函数不会拒绝足够大的缓冲区为止。 这是对可选附件K函数strerror_s中假定的功能的批评。
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以外的错误代码,并显示一些调试信息。您可以根据需要进行调整。
-
如果它对于程序来说具有足够的可移植性,请考虑使用err.h函数系列中的errx或warnx函数。 它来自90年代的BSD,我相信Linux可以做到。 使用printf格式字符串的功能通常很有用。 如果对您有用,则可以用宏将其包装以注入FILE:LINE。
您不能依赖strerror()返回的字符串,因为它可能在下一次调用该函数时发生变化。先前返回的值可能会过时。特别是在多线程环境中,您无法确保在访问字符串时该字符串有效。
想象一下:
1 2 3 4 5
| Thread #1:
char * error = strerror(1);
Thread #2
char * error = strerror(2);
printf(error); |
根据strerror()的实现,此代码将打印出错误代码2的错误代码,而不是错误代码1的错误代码。
-
它会通过什么机制改变?假定字符串是在C运行时库的内部实现中静态定义的,因此不会改变吗?还是那是错误的,它们的确可能会动态变化?
-
你为什么要假设这个?是的,可以这样做,但是可以完全不同-我们不知道;)..
-
主要问题是Posix不需要strerror()是线程安全的。您必须改用strerror_r()。在Windows上,使用strerror_s()。
-
请参阅上面的dfas响应。问题在于,另一个线程可能会更改区域设置,从而导致字符串被卸载和替换,从而导致先前从strerror()返回的结果变为无效的指针。还是正如巴斯蒂安(Bastian)指出的那样:因为该标准说它不是线程安全的。
对于简洁的包装,可以使用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的风险。
-
如果如果它的" const char *"相同的Merry Prankster可能尝试转换为(char *)并在此之后更改代码;)..声明某些内容为" const"并不意味着它不能更改。它只是给编译器一个优化提示。
-
我同意,从纯粹的const-ness角度来看,这是正确的,但是我强烈怀疑缺少const只是历史原因,用户有义务不以与许多字符串相同的方式更改字符串的内容。这样的非const但应该成为标准库的一部分。如果真是这样,那么我仍然看不到strerror()弃用的原因。
-
将其设置为const char *并说不得更改该内容就足以使其线程安全,前提是您确保已同步调用,以使使用不会交错。无论如何,您不能禁止程序员使其程序崩溃。他仍然可以创建数组并写出数组界限。并可能使用Terminate或其他命令杀死其他线程:)将const char *作为返回值可以有效防止意外更改。
-
真正的程序员在ROM中放置了静态const。