这是C语言中的一个历史性设计缺陷,也在C++中重复出现。
它可以追溯到16位计算机时代,错误在于决定使用所有16位来表示大小,最高可达65536,放弃了表示负大小的可能性。
如果unsigned
的含义是“非负整数”(大小在逻辑上不能为负),那么这本身不会是一个错误,但它与语言的转换规则有关,存在问题。
考虑到语言的转换规则,在C语言中,unsigned
类型并不表示非负数,而更像是一个位掩码(数学术语实际上是“ℤ/n环的成员”)。要看到为什么,请考虑以下内容:
unsigned - unsigned
得到一个unsigned
结果
signed + unsigned
得到一个unsigned
结果
如果你把unsigned
理解为“非负数”,那么它们都显然毫无意义。
当然,说一个对象的大小是ℤ/n
环的成员根本就没有任何意义,这就是错误所在。
实际影响:
每当你处理对象的大小时,要小心,因为该值是unsigned
类型,在C/C++中,这种类型具有许多对于数字来说是不合逻辑的属性。请永远记住,unsigned
并不意味着“非负整数”,而是“ℤ/n
代数环的成员”,最危险的是,在混合操作的情况下,一个int
会被转换为unsigned int
,而不是相反。
例如:
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=0; i<pts.size()-1; i++) {
drawLine(pts[i], pts[i+1]);
}
}
这段代码存在缺陷,因为如果传递一个空向量,它将执行非法(UB)操作。原因是pts.size()
是一个unsigned
。
语言规则将把整数1
转换为1{mod n}
,在ℤ/n
中执行减法,结果为(size-1){mod n}
,还会将i
转换为{mod n}
表示,并在ℤ/n
中进行比较。
C/C++实际上在ℤ/n
中定义了<
运算符(在数学中很少使用),即使输入向量为空,您最终仍然会访问pts[0]
、pts[1]
等等直至极大的数字。
正确的循环应该是:
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=1; i<pts.size(); i++) {
drawLine(pts[i-1], pts[i]);
}
}
但我通常更喜欢。
void drawPolyline(const std::vector<P2d>& pts) {
for (int i=0,n=pts.size(); i<n-1; i++) {
drawLine(pts[i], pts[i+1]);
}
}
换句话说,尽快摆脱“unsigned”,只使用常规整数进行操作。永远不要使用“unsigned”表示容器或计数器的大小,因为“unsigned”的意思是“ℤ/n”的成员,而容器的大小不属于这些内容。无符号类型非常有用,但
不适合表示对象的大小。标准的C/C++库不幸地做出了错误的选择,现在已经太晚改正。然而,您不必犯同样的错误。
Bjarne Stroustrup的话:
使用无符号整数代替整数来获得一个额外的比特位表示正整数几乎从来不是一个好主意。通过声明变量为无符号来确保某些值为正数的尝试通常会被隐式转换规则所打败。
printf(" %lu %lu", sizeof(-1), sizeof(SIZE));
试一下吧! - Grijesh Chauhansize_t
ÁĪĽŚěčŤŅõŤ°ĆŚĀáŤģĺ„Äā"%zu"
śėĮś≠£Á°ģÁöĄś†ľŚľŹ„Äā - Jens Gustedt