C++20 使用“operator>>”更改了对字符数组的读取 - 如何解决这个问题?

C++20 changes reading into char arrays with `operator>>` - How to fix this?

提问人:Martin Weitzel 提问时间:6/16/2023 最后编辑:Martin Weitzel 更新时间:6/19/2023 访问量:127

问:

编辑:从评论中总结一下(在我关闭主题之前):

  1. 这个问题之前已经在这里讨论过:

决议是委员会意识到他们用这个破坏了代码。

  1. 我不知道是什么紧急问题导致 LWG 替换旧版本(在 C++98 和 C++17 之间的标准中,即大约 20 年)而不是这样做(恕我直言,它在 C++14 中完成 - 有充分的理由):std::gets

第 1 步:仅添加新的模板化版本,期望“对数组的引用”,在编译时从中推断出元素的数量(这肯定是防止缓冲区溢出的保护)。它将“从第一天起”生效。

第 2 步:弃用标准中从 C++98 到 C++17 的版本,如果存在足够有效的用例来保留它,请等待社区反馈。然后,也许稍后将其删除一个标准。

我认为一个有效的用例是我在这里展示的:https://godbolt.org/z/nG174vnqP

(从真实代码中提取,但缩短以仅显示问题)。Contained 也是一个小演示,为什么我认为旧版本和新版本很可能共存。但也许我的这种评估是错误的。

我发现目前最烦人的是,如果不进入UB-land,就无法解决C++20中的问题。特别是因为我认为“旧”版本在内部仍然可用,而新版本只是转发到它 - 这很可能是因为您不希望每个不同的数组长度都有单独的实现。


随着 C++20 的发布,读取 char 数组的重载现在需要一个参数而不是 .自 C++98 以来正确编译的原始代码(如下所示)将不再起作用:operator>>char(&)[N]char*

std::size_t sz = 10;
char *cp = new char[sz];
...
std::cin >> std::setw(sz) >> cp;

为了更正此问题,可以按如下方式修改代码:

std::cin >> std::setw(sz) >> *reinterpret_cast<char(*)[std::numeric_limits<int>::max()]>(cp));

请参阅此处:https://godbolt.org/z/svPcT4eao

此外,可变长度字符串的常见实现存在一个问题,该字符串可以在编译时无提示地更改而没有指示。

要回答评论中的一般问题,为什么我不使用:std::string

事实上,我用了很多,但我偶尔也会指导那些在你不想增加任何不必要的开销的项目中工作的人,有些人更喜欢不使用三个指针的字符串类,而一个指针就足够了。该示例是从其中之一中提取的。std::string

此外,有人指出,在 C++20 答案中不能将 std::cin 与 char* 或 char[] 一起使用,是的,它是关于同一个主题的,但这个答案中更重要的信息是在它指向的 LWG 中:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0487r1.html

它清楚地表明,有效的 C++17 代码不再编译,但遗憾的是,它没有涵盖之前有效的代码不再有效但不会导致编译时错误的静默更改:

至少考虑到

    struct vbuf {
        std_::size_t sz;
        char cbuf[1];
    };

超分配技术尚未通过早期的 C++ 标准转换为 UB。

在下面的评论中,@n.m. 评论说这已经是 C 中的 UB。他可能是正确的(自 C89 以来我没有检查过所有的 C 标准,而且我相对确定当时它不是 UB),但至少它是一种常见的技术(例如,在 Linux 消息的缓冲区中,参见 sndmsg(3P) 等),因此我认为这是一个安全的假设 - 至少对于 Linux 编译器系列 - 这是定义明确且安全的。

但我不会再声称新版本会导致“无声的变化”,因为如果我们在 UB 土地上,这当然不适用。

C++ 数组输入 C++20 字符指针

评论

2赞 Mooing Duck 6/16/2023
@MartinWeitzel: But in this case the data is coming from external input, so every example you cited doesn't apply. The old code was super dangerous and wildly prone to attacks and undefined behavior, and has been removed. You should not work around it to reenable it, because that would reopen your program to attacks and undefined behavior.
1赞 Martin Weitzel 6/17/2023
@MooingDuck thanks again. What I learned meanwhile from open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0487r1.html is that the fact valid code will not any longer compile after this change was predicted but obviously they not thought of the SILENT CHANGE pointed out in my linked example code. So be it, I must live with the extremely ugly reinterpret_cast now. But some C++ projects using a C-compatible light-weight string class based on over-allocation for struct with a size as first member and a single element array as second may be bitten by it. I need to warn my customers.
1赞 n. m. could be an AI 6/17/2023
It is not hard to write an extractor that doesn't invoke UB like your cast to a huge array does. Construct a sentry, read characters one by one, stop when a whitespace is seen. But perhaps more importantly, no one should ever use for string-like things, the stop-on-whitespace behaviour does more harm than good.>>
2赞 n. m. could be an AI 6/17/2023
"based on over-allocation for struct with a size as first member" this is and always have been UB in C++.
1赞 Eljay 6/17/2023
I'd make a helper wrapper class so you can do , which would encapsulate all the messy and verbose behavior into one spot.into_buffercin >> into_buffer(cp, sz)

答: 暂无答案