【问题标题】:Why can't I use strerror?为什么我不能使用 strerror?
【发布时间】:2010-10-28 08:55:32
【问题描述】:

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

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

危险在哪里?

C 中有没有好的替代方法?

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

[编辑]

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

提前致谢

【问题讨论】:

  • 你不能使用它,因为微软说“该死的 ISO C 标准 - 我们不会让你使用它,除非你用编译指示覆盖警告或错误”。他们还禁止了 memcpy() - 这很荒谬,因为你告诉它要复制多少字节,如果你不能考虑它并且知道在调用 memcpy() 之前目标空间中有足够的空间容纳字节数,您不属于使用 C 或 C++ 编写代码的团队。
  • @JonathanLeffler ISO C 标准有同样的问题,并以同样的方式解决它。他们通过添加strerror_r解决了不安全功能,而微软通过添加strerror_s解决了不安全功能。无论哪种方式:如果你打电话给strerror 你做错了;请停下来。
  • @IanBoyd:ISO C 没有用strerror_r() 解决它;那是一个 POSIX 函数,而不是标准的 C 函数。标准 C 在 C11 和 C18 的附录 K 中将 strerror_s() 定义为可选功能。
  • @JonathanLeffler 我们有交叉编译器标准功能吗?优秀! (一定要弃用旧的)
  • @IanBoyd — 不;我们仍然没有交叉编译器标准功能。没有编译器(库)能够完全准确地实现附件 K,甚至 MS 编译器也不行。 MS 通常不实现 POSIX 特定的功能(例如strerror_r());非 Windows 编译器通常不实现特定于 Windows 的函数(例如 str_error_s())。唯一的跨平台标准函数是strerror(),但显然一些实现不遗余力地使其线程不安全,这似乎有点愚蠢,但该行业并不因其对常识的坚持而闻名。

标签: c++ c deprecated


【解决方案1】:

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

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

【讨论】:

  • 呃,问题是:为什么它被认为是不安全的?你什么也没告诉我。
  • 确实告诉了你原因。它适用于内部静态缓冲区 - 即跨线程共享的缓冲区。这是不安全的。
  • 啊,是的。我很抱歉。现在,对整个世界来说,下一个问题是:为什么会有人这样实现它!?
  • JamieH,该函数可以高效地实现为 char *msg[] = { "file not found", "you are ill", "you are not root", "time is over" } ; char *strerror(int n) { return msg[n]; }
  • 一切都与语言环境有关——从 strerror 返回的字符串需要保持有效,直到再次调用 strerror,即使您更改了语言环境(这可能会卸载旧的语言环境)。因此,将指针返回到语言环境数据意味着实现需要保留旧的语言环境。我认为这是可行的,但并不像你想象的那么简单。
【解决方案2】:

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

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

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

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

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

【讨论】:

  • “在过去线程之前,它根本不是问题”。它也不是可重入的,但它从未声称可以从信号处理程序中调用。
  • 没问题,只需longjmp 来自信号处理程序;)
  • 请注意,没有可用于预分配strerror_r 输出缓冲区的安全大小。技术上正确的程序必须使用 malloc 循环,直到函数不拒绝足够大的缓冲区。这是对据称在可选附件 K 函数 strerror_s 中解决的函数的批评。
  • @ConradMeyer 理论上是的,但例如 Linux 手册页指出 The GNU C Library uses a buffer of 1024 characters for strerror(). This buffer size therefore should be sufficient to avoid an ERANGE error when calling strerror_r() and strerror_l().
  • @Claudiu OP的问题源于将软件移植到Windows,所以我认为在这种情况下可能需要Linux以外的可移植性。
【解决方案3】:

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

【讨论】:

  • 考虑使用err.h 系列函数中的errxwarnx 函数,如果它对您的程序来说足够可移植的话。它来自 90 年代的 BSD,我相信 Linux 拥有它。使用 printf 格式字符串的能力通常很有用。如果对你有用的话,你可以用一个宏包装它来注入 FILE:LINE。
【解决方案4】:

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

想象一下:

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()。
  • 见上面 dfa 的回复。问题是另一个线程可能会更改语言环境,导致字符串被卸载和替换,导致之前从 strerror() 返回的指针变为无效指针。或者正如巴斯蒂安指出的那样:因为标准说它不是线程安全的。
【解决方案5】:

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

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

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

他们似乎对缺陷的发布周期非常快,所以你可以尝试请求一个模组?

【讨论】:

    【解决方案6】:

    我理解其他答案,但我认为用代码显示更清楚。

    检查 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 是全局变量。所以当再次调用strerror 时,它的内容可能会改变。这就是为什么它是线程不安全的。

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 如果它是“const char *”,同样的 Merry Prankster 可能会尝试转换为 (char *) 并在此之后更改代码;) .. 将某些东西声明为“const”并不意味着它无法更改。它只是给编译器一个优化提示。
      • 我同意,从纯 const 的角度来看,这是正确的,但我强烈怀疑缺少 const 只是历史性的,用户有义务不要更改字符串的内容与标准库中许多这样的非常量但应该是部分的方式相同。 如果是这种情况,那么我仍然认为 strerror() 没有被弃用的理由。
      • 将其设为 const char* 并说不能更改该内容足以使其线程安全 如果 您确保已同步调用以便使用它不要交错。无论如何,你不能禁止程序员让他的程序崩溃。他仍然可以创建一个数组并写出数组边界。并且可以使用 Terminate 或其他东西杀死其他线程 :) 将返回值设为 const char* 可以有效防止意外更改。
      • 真正的程序员将静态常量放入 ROM。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-18
      • 2010-10-28
      • 2011-03-26
      • 2022-01-03
      • 2012-07-16
      • 2012-03-13
      • 2022-01-04
      相关资源
      最近更新 更多