为什么编译器不使无符号与有符号的比较安全?



正如我们所知,这样的代码会生成一个警告:

for (int i = 0; i < v.size(); ++i)

解决方案类似于auto i = 0u;decltype(v.size())std::vector<int>::size_type,但假设我们被迫同时拥有有符号和无符号值。编译器会自动将int强制转换为unsigned int(实际类型无关紧要)。static_cast<unsigned int>(i)使用显式强制转换使警告消失,但这很糟糕,因为它只做了与编译器相同的事情,并使一个重要的警告静音!

更好的解决方案是:

if ((i < 0) || (static_cast<unsigned int>(i) < v.size()))

可以理解的是,C"更接近金属",因此更不安全。但在C++中,这是没有任何借口的。随着C++和C的分歧(正如他们多年来所做的那样),对C++的数百项改进提高了安全性。我非常怀疑这样的改变也会影响表现。

编译器不自动执行此操作是有原因的吗?

N.b:这确实发生在现实世界中。看见漏洞说明VU#159523:

Adobe Flash中出现此漏洞是因为Flash将带符号整数传递给calloc()。攻击者可以控制此整数,并可以发送负数。由于calloc()采用无符号的size_t,负数将转换为一个非常大的数字,该数字通常太大而无法分配,因此callog()返回NULL,导致漏洞存在。

C++的一个重要目标是兼容性,如果有符号/无符号混合是一个致命错误,那么大量代码将无法编译。参见1986年Stroustrup的C++设计目标。

您的提案还添加了来源中没有的比较。

可以说,在C++11中,如果您使用ranged for和auto:,这种情况已经变得更安全了

for (auto i : v)

当前用于隐式转换的C++规则使得无符号整数类型用作数字类型是危险的。典型的例子,string("hello").size() < -5"是有保证的,这很傻–但不是只是愚蠢。追踪涉及此类转换的细微错误所浪费的时间,更不用说试图选择完全匹配的类型所浪费的精力了,这表明这比虚构的例子中的愚蠢行为要严重得多;这与编程中的问题一样严重。

一个糟糕的解决方案是通过添加额外的比较来容纳草率的程序员,这些程序员直接比较有符号的和无符号的。这很糟糕,因为C++是建立在迎合有能力的程序员的理念之上的,这些程序员知道自己在做什么,并且希望他们的代码和生成的机器代码之间尽可能少。在当前的C++中,比较有符号的和无符号的有简单定义的效果,而不是Java程序员所期望的效果。

一个好的解决方案是让程序员改进,直接表达他或她想要的东西,而不是依赖神奇的隐形修复。

例如,表达意图的一个好方法是将size运算的结果强制转换为签名类型,例如ptrdiff_t,或者在不需要完全通用性的情况下,只需普通的int

支持这一点的一个好方法是定义这样的n_items函数:

using Size = ptrdiff_t;
template< class Container >
auto n_items( Container& c )
    -> Size
{ return c.size(); }

以不支持例如std::list为代价,通过使用end(c) - begin(c),可以使其在没有专业化的情况下适用于原始阵列。但我更喜欢单独的(1)专业化。像这样:

template< class Item, Size n >
auto n_items( Item (&)[n] )
    -> Size
{ return n; }

免责声明:即兴代码,不被编译器的手触摸。


1)从技术上讲,这是一种过载,而不是专业化。但我遵循前标准化委员会秘书Pete Becker的术语指导。也就是说,让我们尽可能使用简单的描述性语言,只有在必要的时候才使用正式的术语。

最新更新