【问题标题】:Using sprintf with std::string in C++在 C++ 中使用 sprintf 和 std::string
【发布时间】:2016-08-22 21:11:17
【问题描述】:

我在 C++ 11 中使用sprintf 函数,方式如下:

std::string toString()
{
    std::string output;
    uint32_t strSize=512;
    do
    {
        output.reserve(strSize);
        int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
            INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
            FORAMT_VERSION,
            contentType,
            contentFormatVersion,
            magic,
            segmentId);

        strSize *= 2;
    } while (ret < 0);

    return output;
}

除了每次检查预留空间是否足够之外,还有更好的方法吗?为了将来添加更多东西的可能性。

【问题讨论】:

  • 你在使用snprintf吗?因为sprintf,如您的代码所示,无法确定缓冲区大小。 snprintf 也会返回所需的缓冲区大小,因此您可以将返回值 +1 用作新的 strSize
  • 这段代码非常错误。 reserve 不会改变字符串的大小,sprintf 不会因为你写的越界而返回负数。你需要分配你需要的空间之前写出边界。

标签: c++ string c++11


【解决方案1】:

你的构造——写入到从c_str()接收的缓冲区——是未定义的行为,即使你事先检查了字符串的容量。 (返回值是一个指向 const char 的指针,函数本身标记为 const,这是有原因的。)

不要混合使用 C 和 C++,尤其是不适合写入内部对象表示。 (这破坏了非常基本的 OOP。)使用 C++,以确保类型安全,并且不会遇到转换说明符/参数不匹配,如果没有别的。

std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  << " Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

替代方案:

std::string output = "Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
                   + " Version=" + std::to_string( FORMAT_VERSION )
                   // ...and so on...
                   ;

【讨论】:

  • 如何模拟ostringstream中的字符串格式,比如magic=%04x等等?
  • @danieltheman: setw, setfill.
  • 你的评论'返回值是一个指向const char的指针,函数本身标记为const,原因是' 表示由于const 限定符与sprintf(output.c_str(), ... 表达式不匹配,呈现的代码甚至不应编译。所以这是错误的 OP 正在使用 sprintf 他描述的方式,因为呈现的摘录不是可运行的代码......
  • @CiaPan 从技术上讲,它并不意味着代码甚至不应该编译。如果我们假设从 const 到 non-const 的隐式转换是不正确的,那么它意味着编译器不是需要来编译代码,但编译器可以支持转换为非-标准语言扩展。如果程序按照标准格式不正确,编译器只需向用户提供诊断消息。 如果编译成功,那么通过const指针写入的就是UB。
  • @user2079303 知道了。谢谢你的解释,我认为编译器不允许接受这样的代码。
【解决方案2】:

其他答案中显示的 C++ 模式更好,但为了完整起见,这是sprintf 的正确方法:

auto format = "your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);

注意

  • 你必须resize你的字符串。 reserve 不会改变缓冲区的大小。在我的示例中,我直接构造了正确大小的字符串。
  • c_str() 返回 const char*。您不得将其传递给sprintf
  • std::string 缓冲区在 C++11 之前不能保证是连续的,这依赖于该保证。如果您需要支持对 std::string 使用绳索实现的奇异的 C++11 前兼容平台,那么您最好先sprinting 进入 std::vector&lt;char&gt;,然后将向量复制到字符串。
  • 这仅适用于在大小计算和格式化之间未修改参数的情况;对多线程代码使用变量的本地副本或线程同步原语。

【讨论】:

  • 有趣;不过,我不太确定构造(写信给&amp;output[0])是否符合标准的规定。这可能适用于 std::string 的所有实现,但至于明确定义,我有疑问。
  • @DevSolar 如果我没记错的话,std::string 缓冲区的连续性是从 c++11 开始保证的。在此之前,不能保证。据我所知,没有其他原因导致此操作无效。
  • @user2079303 获取数字的字符串构造函数?我认为c ++中没有这样的东西,你能详细说明一下吗?我在说这个:std::string output(size + 1);
  • @danieltheman 抱歉,我的意思是使用带有大小的构造函数和 char。我忘记了第二个参数没有默认值。代码现已修复。
  • 您不想使用std::snprintf(&amp;output[0], size, format, /* args go here */); 而不是最后一行更安全吗?想象一下,另一个线程在 auto size = ... 赋值和最后的 std::sprintf() 调用之间修改了一个格式字符串 args(例如,一个整数从 9 变为 10,从而多占用一个字符)。
【解决方案3】:

我们可以从这里https://stackoverflow.com/a/36909699/2667451 和这里https://stackoverflow.com/a/7257307 混合代码,结果将是这样的:

template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
    std::string output(size + 1, '\0');
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
    return output;
}

【讨论】:

    【解决方案4】:

    您的代码错误。 reserve 为字符串分配内存,但不改变其大小。写入c_str 返回的缓冲区也不会改变其大小。所以字符串仍然认为它的大小为 0,并且您刚刚在字符串缓冲区的未使用空间中写入了一些内容。 (可能。从技术上讲,代码具有未定义的行为,因为写入c_str 是未定义的,所以任何事情都可能发生)。

    你真正想做的是忘记sprintf和类似的C风格函数,而使用C++的字符串格式化方式——字符串流:

    std::ostringstream ss;
    ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
       << " Version=" << FORAMT_VERSION
       << /* ... the rest ... */;
    return ss.str();
    

    【讨论】:

      【解决方案5】:

      更好的方法是使用{fmt} library。例如:

      std::string message = fmt::sprintf("The answer is %d", 42);
      

      它还提供了比 iostreams 和 printf 更好的接口。例如:

      std::string message = fmt::format("The answer is {}", 42);`
      

      见:
      https://github.com/fmtlib/fmt
      http://fmtlib.net/latest/api.html#printf-formatting-functions

      【讨论】:

      • 第三方库...?
      【解决方案6】:

      是的,有!

      在 C 中,更好的方法是将文件与 null 设备相关联,并将所需输出的虚拟printf 发送给它,以了解如果实际打印会占用多少空间.然后分配适当的缓冲区和sprintf相同的数据给它。

      在 C++ 中,您也可以将输出流与空设备相关联,并测试使用std::ostream::tellp 打印的字符数。但是,使用 ostringstream 是一种更好的解决方案 - 请参阅 DevSolar 或 Angew 的答案。

      【讨论】:

      • 完全忽略了你不能写入c_str()返回的缓冲区的点,即使那样也不必要的复杂,因为有snprintf()/snprintf_s
      • snprintf 比 null device hack 更好。但是你必须为swprintf 使用那个hack。
      【解决方案7】:

      您可以将sprintf() 的实现用于我编写的std::string,它在后台使用vsnprintf()

      它将格式字符串拆分为纯文本部分,这些部分只是复制到目标std::string 和格式字段部分(例如%5.2lf),它们首先vsnprintf()ed 到缓冲区中,然后附加到目的地。

      https://gitlab.com/eltomito/bodacious-sprintf

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-29
        • 2015-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多