【问题标题】:VC++ wcscpy_s randomly assert on "Buffer is too small"VC++ wcscpy_s 随机断言“缓冲区太小”
【发布时间】:2023-03-30 05:56:01
【问题描述】:

你可以从下面的代码中看到发送给函数的参数没有超过缓冲区大小。

这个问题是随机发生的,而且只发生在调试版本中。

#include <thread>
#include <sstream>

#define BUF_SZ 32

int main()
{
  wchar_t src[BUF_SZ]{};
  bool running = true;
  std::thread th([&] {
    for (double g = 0; g < 100000; g += .1)
    {
      std::wstringstream ws;
      ws << g;
      wcscpy_s(src, BUF_SZ, ws.str().c_str());
    }
    running = false;
  });

  wchar_t dst[BUF_SZ]{};
  while (running)
    wcscpy_s(dst, src);  // assert on "Buffer is too small" randomly

  th.join();
  return 0;
}

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 随着优化调高,我认为您的代码永远不会退出。该运行变量需要设置为 std::atomic。因为它是优化器,所以它不会包含它,因为它在它存在的每个线程中都没有可见的效果。
  • @ZanLynx,程序退出,这是一个简单的SRSW(单读单写)无锁演示。
  • 它不仅仅是无锁的,它是布尔无读的。查看 godbolt.org/z/qgsm7r 和汇编输出中的第 249 行。是的,我修改了代码以使用它并对其进行反lambda,如果这是一个词的话。但是,一旦我将 wcscpy_s 定义为内联函数,编译器就会“帮助”删除 running 布尔值的“无用”读取。
  • @eihero 对于您的程序目前来说这不是问题,因为您在其他库中执行编译器视图之外的操作。如果该代码对编译器可见,那么它肯定会。在那个godbolt 示例中,您可以看到它正在完成。它只是简单地循环内联 wcscpy_s 函数,而无需读取 running。我只是想在这里为您的未来提供帮助。

标签: c++ windows visual-studio visual-c++


【解决方案1】:

由于字符串副本需要复制“src”字符和终止空字符,因此您需要提供一个缓冲区,该缓冲区至少比“src”大小的一个字符大。

我建议你可以尝试使用:

    wcscpy_s(dst, sizeof src+1, src); 

【讨论】:

  • src缓冲区被初始化并填充为0,并且g的转换后的字符串大小永远不会超过7,它总是在任何时候以null终止。
  • 调试模式下的 wcscpy_s 已检查指定的长度 (strlen(name) 不足以适合名称 + 终止 \0,因此它断言。如果 dest 或 src 是空指针,或者如果目标字符串大小 dest_size 太小,则调用无效参数处理程序。在 C++ 中,通过模板重载简化了这些函数的使用,模板重载可以自动推断缓冲区长度,因此您不必指定大小参数,并且它们可以自动将旧的、安全性较低的功能替换为更新的、更安全的功能。
  • 我明白你的意思,但这不是问题所在。正如我所说,缓冲区有 32 个元素,转换后的字符串永远不会超过 7,strlen(name) 应该总是足以容纳名称 + 终止 \0。该代码仅用于演示。这是一个简单的 SRSW(single reader, single writer) 问题,读者只关心获取最新数据。希望您可以将此问题报告给 Visual C++ 团队。谢谢。
  • 我建议您可以将问题发布到Developer Community 以获得更好的帮助。
  • 我已经发布了 MSFT VC++ 团队的解决方案,感谢您提供的信息。
【解决方案2】:

感谢 MSFT VC++ 团队的 Steve Wishnousky 先生,这里是问题的完整解释。

Wcscpy_s 不会对缓冲区进行原子操作,只会工作 如果缓冲区在运行时不更改内容,则正确 wcscpy_s.

另外需要注意的是,在 Debug 模式下,wcscpy_s 函数 将使用调试标记 (0xFE) 填充缓冲区的其余部分以 表明那里的数据现在无法假设它的内容, 以检测潜在的运行时错误。

当然,错误每次都会发生不同的情况,但让我们假设 当 src=1269.9 并调用 wcscpy_s(dst, src) 时会发生此错误。 src 的实际内容是:“1 2 6 9 . 9 null 0xfe 0xfe ...”。 wcscpy_s 复制了 1269.9 但因为它即将读取空值, 其他 wcscpy_s 刚刚向 src 写入了一个新值,所以现在是:“1 2 7 0 null 0xfe 0xfe ...”。而不是读取对应的null 之前的src,它读取的是0xfe,所以它认为这是一个真实的 特点。因为在我们到达末尾之前没有空终止符 缓冲区,调试运行时断言缓冲区太小 用于输入。

在发布版本中,0xFE 调试标记未放置在 缓冲区,所以它最终会找到一个空字符。你也可以 通过调用 _CrtSetDebugFillThreshold 禁用调试标记: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetdebugfillthreshold?view=vs-2019.

请注意,调试标记实际上是在捕捉真正的正确性 不过这里有问题。此“在 wcscpy_s 期间更改缓冲区”问题可能 发生任何价值。例如,如果 src=1269.9,wcscpy_s 可以复制 超过 126,但是当它即将读取 9 时,src 更新为 1270 并且最终在 dest 中的值将是“1260”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-03
    相关资源
    最近更新 更多