C++:“cout << pointer << ++pointer” 会产生编译器警告

6

我这里有一个C++学习演示:

char c = 'M';
short s = 10;
long l = 1002;
char * cptr = &c;
short * sptr = &s;
long * lptr = &l;
cout << "cptr:\t" << static_cast<void*>(cptr) << '\n';
cout << "cptr++:\t" << static_cast<void*>(++cptr) << '\n';
cout << "sptr:\t" << sptr << '\n';
cout << "sptr++:\t" << ++sptr << '\n';
cout << "lptr:\t" << lptr << '\n';
cout << "lptr++:\t" << ++lptr << '\n';

cout << c << '\t' << static_cast<void*>(cptr) << '\t' << static_cast<void*>(++cptr) << '\n';
cout << s << '\t' << sptr << '\t' << ++sptr << '\n';
cout<< l << '\t' << lptr << '\t'<< ++lptr << '\n';

编译器警告:

image

有人能向我解释一下吗?如何修复它?


3
我不是专家,但我怀疑这可能与https://dev59.com/Zm855IYBdhLWcg3wuG-W有关,如果不是重复的话。 - Tas
4
最好直接贴上警告以进行搜索。 - dao leno
将cout语句拆分成多行以强制执行顺序。 - user1196549
@Tas,你能指出那个问答中涵盖了这个问题中的代码吗? - M.M
@M.M,您在这方面显然更加专业,我已经为您的答案点赞了。但是,在第一个答案中提到的评估顺序和一个对象在单个语句中只被访问一次的部分,我认为printf代码演示了同样的问题。当我看到这个问题并阅读了那篇文章时,我认为它非常相关,如果不是答案的话,但我没有试图关闭这个问题,只是链接了我认为可能有用的东西。 - Tas
1
叹气,重新开放。所谓的重复问题并未涵盖此代码,我对那些匆忙关闭东西的低效用户感到非常不满,仅因为与另一个问题有几个相同的单词。 - M.M
3个回答

8

自 C++17 以来,此代码正确无误

在 C++17 之前,对于一个 << 链的操作数没有次序,因此这段代码会导致未定义行为。

编译器警告提示您未处于 C++17 模式。要解决此问题,您可以选择:

  • 使用 C++17 模式进行编译;或者
  • << 链分成多个 cout << 语句,其中不存在在同一语句中的 x++x

注:截至目前,所有版本的 g++ 程序都存在缺陷,未正确实现这些操作的次序要求,请参见此线程获取更多示例。这些警告可视为指示编译器错误;它们并不是虚假警告。


为什么使用-std=c++17 -Wall选项仍然显示警告?难道不应该被-std=c++17所抑制吗?或者-Wall即使被标准抑制了,也可以发出警告吗?=> compiler explorer - sandthorn
4
@sandthorn,“被标准压制”的说法是不存在的。标准允许任何实现出于任何原因警告任何代码。诊断质量不影响符合性。话虽如此,如果警告提供的信息是不正确的,值得将其报告为一个 bug。实现者确实希望提供良好的诊断。 - user743382
@hvd 发现即使使用 std=c++11,GCC 也不会警告,除非加上 -Wall。而clang则无论如何都会发出警告。=> 编译器浏览器 - sandthorn
2
同意应该为clang 6和gcc 8.1报告一个bug,因为它们会产生无用的警告。 - M.M

1
根据C++标准草案 N4762 (2018-07-07) 第68页第 § 6.8.1/10 节所述(或者在 eel.is 网站 这里[intro.execution]/10 节),除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值顺序是未指定的。

For语句

cout << c << '\t' << static_cast<void*>(cptr) << '\t' << static_cast<void*>(++cptr) << '\n';

这意味着C++编译器不能保证在同一个语句中,static_cast<void*>(cptr)会在右边的++cptr之前被评估,因为它们都是操作数。因此,您可以通过将它们按顺序放入有序且分离的语句中来强制它们的顺序执行。
例如:
cout << c << '\t' << static_cast<void*>(cptr) << '\t'; cout << static_cast<void*>(++cptr) << '\n';

[编译器探索者]


更新

正如M.M的回答所说,c++17现在保证了<<运算符的操作数求值顺序

事实证明,即使使用std=c++11,GCC 8.1也不会发出警告,除非加上-Wall选项,在加上-Wall选项时总是会发出警告

而clang 6.0无论什么情况下都会发出警告。

[ 编译器浏览器 ]

因此,除了使用-std=c++17外,还必须提供选项-Wno-unsequenced来抑制警告:

  • 如果你正在使用clang 6.0
  • 如果你正在使用带有-Wall选项的gcc 8.1

@M.M 我加了“也”,但如果仍然不明确,您可以编辑我的内容。或者您认为即使有警告,使用-std=c++17编译器仍然不能保证<<操作数的求值顺序? - sandthorn
@M.M 我将措辞从“除了”改为“以及”。 - sandthorn

1

您在第18、19、20行代码中出现了未定义的行为。执行该行代码的结果取决于先评估ptr还是++ptr,因此结果会有所不同。


虽然我同意这可能是正确答案,但你可以通过解释为什么不能保证一个会在另一个之前被评估来改进你的答案。这是编译器的原因还是由标准指定?我们应该如何修复它? - Tas
那么怎么修复呢? - K.F
@K.F 原来GCC即使使用std=c++11也不会发出警告,除非加上-Wall选项。而clang则无论如何都会发出警告。=> compiler explorer 因此,在clang的情况下,您必须提供-Wunsequenced选项来抑制它。 - sandthorn
@sandthorn 在clang的情况下,似乎-Wunsequenced选项无法抑制警告。你试过了吗? - K.F
@K.F 对不起,由于时间有限的评论修复政策,我的错误应该是“-Wno-unsequenced”。抱歉。 - sandthorn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接