C++中编译器优化导致的一个问题

今天在 MSVC2015 上在用 for 遍历 std::vector<T> 时遇到一个 Access Violation 错误,关键代码如下

1
2
3
4
for (auto i = v.size() - 1; i >= 0; i--)
{
// ...
}

原因是 C 语言中一个符号数和一个无符号数进行运算时首先要将符号数转为无符号数,所以编译器会自动推导 i 的类型为 auto = size_t 。而对于无符号整数,编译器认为 i >= 0 是一直成立的,如果循环体中不使用 i ,编译器很可能会优化成死循环。
此外,即使编译器不做优化这个代码也不正确,当 v.size() 为0时,无论是有符号的减法还是无符号的减法,i 的二进制表示都会是 0xffffffff。而 0xffffffff 在无符号数里面是 UINT32_MAX,而在有符号数里面是-1。于是将代码改成下面这样子后运行就正常了。

1
2
3
4
5
int vsize = v.size(); // 转换为有符号数
for (auto i = vsize - 1; i >= 0; i--)
{
s.push(make_pair(v[i], deep + 1));
}

在使用无符号类型时需要特别注意溢出问题,还有一个常见的错误来自于从有符号数到无符号数的 narrowing conversion,在 CSAPP2.2.8 中给出了一个 getpeername 中的漏洞以说明窄化转换造成的非法读取。

使用 auto 时坑还是比较多的,另一个常见的就是 std::vector<bool> 的超级大坑。众所周知 std::vector<bool> 被全特化了。全特化原因很容易理解,节省空间嘛,搞成一个动态的 bitset。由此带来的影响是 reference 类型不再是 bool & 而是一个代理类,在 MSVC2015 中是 _Vb_reference,通过 _Vb_reference 重载的 opertor=operator bool 进行读写。这时候用 auto & = vec[0]就会报错,原因是不能将左值引用绑定到右值上(不过 MSVC2015 很著名地允许这种写法)。