【问题标题】:std::string constructor with lvalue throws with clang带有左值的 std::string 构造函数用 clang 抛出
【发布时间】:2020-05-10 23:14:10
【问题描述】:

我正在使用 Matei David 的 handy C++ wrapper for zlib,但在 macOS 上编译时出现错误(clang-1100.0.33。

 include/strict_fstream.hpp:39:37: error: cannot initialize a parameter of type 'const char *' with an lvalue of type 'int'

问题出在这里:

/// Overload of error-reporting function, to enable use with VS.
/// Ref: http://stackoverflow.com/a/901316/717706
static std::string strerror()
{
    std::string buff(80, '\0');

    // options for other environments omitted, where error message is set
    // if not Win32 or _POSIX_C_SOURCE >= 200112L, error message is left empty.

    auto p = strerror_r(errno, &buff[0], buff.size());

    // error next line
    std::string tmp(p,  std::strlen(p));
    std::swap(buff, tmp);
    buff.resize(buff.find('\0'));
    return buff;
}

(IIUC与zlib无关,只是试图以线程安全的方式报告错误)。

如果我改成这样:

static std::string strerror()
{
    std::string buff(80, '\0');

    auto p = strerror_r(errno, &buff[0], buff.size());

    // "fix" below
    size_t length = buff.size();
    std::string tmp(p,  length);
    std::swap(buff, tmp);

    buff.resize(buff.find('\0'));
    return buff;
}

我的程序编译并运行良好。

我有两个问题:

  1. 为什么clang不喜欢构造函数std::string tmp(p, std::strlen(p));

  2. 缓冲区在函数开头声明为长度 80。为什么我们还要费心查找长度?

  3. 2的答案可能会回答这个问题,但是我的版本有问题吗?

谢谢。

【问题讨论】:

  • 查看strerror_r 的文档。它有两个完全不同的版本,具有不同的返回值。你有一个返回int 的。这就是您的 strerror_r 显然返回的内容,并且从 int 构造 std::string,因为错误消息清楚地抱怨,很明显,不会有好的结果。
  • 有一个叫做strerror的ISO C(和包含的ISO C++)函数;为避免混淆,最好将此函数重命名为其他名称
  • 是的,谢谢@M.M,它实际上在原始代码的命名空间中。我的代码修剪有点激进。

标签: c++ clang lvalue strerror


【解决方案1】:

如果你使用int strerror_r(int errnum, char *buf, size_t buflen);,那么没有合适的字符串构造函数,程序格式不正确。

如果你使用char *strerror_r(int errnum, char *buf, size_t buflen);,那么程序是良构的。

标准 C/POSIX 库实现会影响您获得的功能。编译器只涉及影响默认情况下可以使用的系统库。

前一个函数是对 XSI 中指定的 POSIX 的扩展(本质上是 POSIX 的一个可选部分),后一个函数是一个 GNU 扩展。

如果您使用 glibc(我不知道这是否是 MacOS 上的一个选项),您可以控制使用宏获得的版本,尽管旧版本中不提供符合 XSI 的版本。它的文档说:

在以下情况下提供符合 XSI 的 strerror_r() 版本: (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)&& ! _GNU_SOURCE


  1. 缓冲区在函数开头声明为长度 80。为什么我们还要费心查找长度?

std::string tmp(p, std::strlen(p)); 的构造中,strlen 对我来说似乎完全没有必要。 std::string tmp(p); 是等价的。


如果您不需要线程安全,那么最便携的解决方案是使用标准 C++ 中的std::strerror

return std::strerror(errno); // yes, it's this simple

如果您确实需要线程安全,那么您可以使用互斥锁将其包装在关键部分中。


请注意,当使用标准库时,函数的名称strerror 保留给全局命名空间中的语言实现。该函数应该在命名空间中,或者被重命名。

【讨论】:

  • 我想可以基于重载或适用于p 两个版本的任何东西制作“可移植”代码
  • 知道了。我的问题中引用的原始代码确实有几个版本,一个用于符合 POSIX 的 strerror,另一个用于 GCC strerror。看来我需要调整编译器指令以捕捉 clang 想要 POSIX 的事实。
  • 或者,更确切地说,不是 clang 想要 POSIX 版本,而是我拥有的 libc 版本是 POSIX 兼容版本。
【解决方案2】:

strerror_r 有两个不同的版本,您通常会看到:

  • 符合 POSIX 的版本,始终将错误消息存储在提供的缓冲区中(如果成功)并返回 int0 表示成功,非零表示错误)
  • GNU 版本可能将错误消息存储在提供的缓冲区中,也可能不存储。它返回一个指向错误消息的char*,该错误消息可能指向用户提供的缓冲区,也可能指向其他一些全局静态存储。

strerror 函数显然是为与strerror_r 的 GNU 版本一起工作而编写的。


至于你的第二个问题,你需要strlenbuff 的长度为 80 个字符,但实际的错误消息可能更短,并且仅部分填充缓冲区。 strlen 被用来从末尾修剪掉任何多余的 nul 字符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-20
    • 1970-01-01
    • 2019-08-27
    • 2015-04-26
    相关资源
    最近更新 更多