Visual Studio 2019中std::map::operator[]是否存在非预期优化问题?
问题本质
你遇到的现象是C++标准对赋值操作数的求值顺序规定变化导致的,不属于MSVC的非预期缺陷。
核心原理
你代码里的核心语句是:
map[key] = map.size();
std::map::operator[]的行为是:如果指定key不存在,会自动插入默认构造的value,并将map的大小+1,返回插入后value的引用。
而在C++17标准正式发布之前,赋值运算符左右两个操作数的求值顺序是未指定的,编译器可以自主选择求值顺序:
- 若先对右操作数
map.size()求值:此时key还未插入,得到size为0,再执行左操作数map[key]插入key、size变为1,最后赋值0,就是你debug模式下看到的符合预期的结果。 - 若先对左操作数
map[key]求值:此时key直接插入,size变为1,再对右操作数map.size()求值得到1,最后赋值1,就是你release模式下的输出结果。
debug模式下编译器关闭了指令重排优化,会按照代码书写顺序求值,而release模式下编译器为了提升执行效率调整了求值顺序,两种行为在C++17之前的标准下都是合规的。
C++17之后的规则变化
C17标准明确规定:对于赋值表达式E1 = E2,右操作数E2的所有值计算和副作用,都早于左操作数E1的所有值计算和副作用。也就是说在符合C17标准的实现中,这段代码的行为是确定的,输出一定为key:1 value:0。
你测试clang没有复现问题,是因为clang默认启用了更高版本的C标准,而MSVC 2019默认使用C14标准,仍允许两种求值顺序存在。
解决方案
如果要保证全平台、全标准版本下行为一致,不要将插入操作和size查询放在同一个赋值语句中,拆分语句明确执行顺序即可:
void dummy(Map& map, Key key) { size_t curr_size = map.size(); map[key] = curr_size; }
如果你的项目已经确定使用C++17及更高标准,也可以在MSVC的编译选项中添加/std:c++17或/std:c++20参数,即可得到符合预期的确定结果。
内容的提问来源于stack exchange,提问作者tm.crz
相关产品推荐
相关产品推荐

