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很著名地允许这种写法)。