CC中的算术及其陷阱

2023-3-23 来源:不详 浏览次数:

概述

无符号数和有符号数是通用的计算机概念,具体到编程语言上则各有各的不同,程序员是解决实际问题的,所以必须熟悉编程语言中的整数。C/C++有自己特殊的算术运算规则,如整型提升和寻常算术转换,并且存在大量未定义行为,一不小心就会产生bug,解决这些bug的最好方法就是熟悉整数性质以避免bug。

我不是语言律师(非贬义),对C/C++算术特性的了解主要来自教材和互联网,但基本上都查阅C/C++标准验证过,C和C++在整数性质和算术运算上应该是完全相同的,如果有错误请指正。

C/C++整数的阴暗角落

C/C++期望自己可以在所有机器上运行,因此不能在语言层面上把整数的编码、性质、运算规定死,这让C/C++中存在许多未规定的阴暗角落和未定义行为。许多东西依赖于编译器、操作系统和处理器,这里通称为运行平台。

标准没有规定整数的编码,编码方式依赖于运行平台。

char是否有符号依赖于运行平台,编译器有选项可以控制,如GCC的-fsign-char。

移位大小必须小于整数宽度,否则是未定义行为。

无符号数左移K位结果为原来的2^K次方,右移K位结果为原来的数除2^K次方。仅允许对值非负的有符号数左移右移,运算结果同上,对负数移位是未定义的。

标准仅规定了标准内置整数类型(如int等)的最小宽度和大小关系(如long不能小于),但未规定具体大小,如果要用固定大小的整数,请使用拓展整数类型(如uint32_t)等。

无符号数的溢出是合法的,有符号数溢出是未定义行为

整型字面量

常常有人说C/C++中的整数字面量类型是,但这种说法是错误的。C/C++整形字面量究竟是什么类型取决于字面量的格式和大小。StackOverflow上有人问为什么在C++中(-)返回true,代码片段如下:

if(-){std::cout"true";}else{std::cout"false";}

现在让我们来探索为什么负数会大于0。一眼看过去,-似乎是一个字面量(32位有符号数的最小值),是一个合法的型变量。但根据C99标准,字面量完全由十进制()、八进制(0)、十六进制(0x)标识符组成,因此可以认为只有非负整数才是字面量,负数是字面量的逆元。在上面的例子中,是字面量,是字面量的逆元。

字面量的类型取决于字面量的格式和大小,C++11(N.14.2)规则如下:

对于十进制字面量,编译器自动在、、longlong中查找可以容纳该字面量的最小类型,如果内置整型无法表示该值,在拓展整型中查找能表示该值的最小类型;对于八进制、十六进制字面量,有符号整型无法表示时会选择无符号类型。如果没有足够大的内置/拓展整型,程序是错误的,GCC/Clang会发出警告。

在C89/C++98没有和拓展整型,因此在查找完后查找unsignedlong。

现在再看上面的代码段就很清晰了,在64位机上,不论是C89/C++98还是C99/C++11,都能找到容纳该值的类型(8字节),因此打印false。在32位机上,占据4个字节无法容纳字面量,在C89/C++98下,的类型为,逆元是一个正数(2^32-),打印;在C99/C++11下,的类型为,逆元是一个负数(-),打印。

经过以上分析,可以判断出提问者是在32位机上使用C++98做的实验。

和字面量有关的另一个有意思的问题是INT_MIN的表示方法。《深入理解计算机系统(第3版)》2.2.6中介绍的代码如下:

/*Minimumandmaximumvaluesa‘signedint’canhold.*/#defineINT_MAX#defineINT_MIN(-INT_MAX-1)

《深入理解计算机系统》没有给出解释,但这种写法很显然为了避免宏被推导为(C99/C++11)或(C89/C++98)。

整型提升与寻常算术转换

再看一个stackoverflow上的提问Implicittypepromotionrules,通过这个例子来了解C/C++算术运算中的整型提升(integerpromotion)和寻常算术转换(usualarithmeticconversion)。提问者编写了以下两段代码,发现在第一段代码中,(1-2)0,而在第二段代码中(1-2)0。这种奇怪的现象就是整型提升和寻常算术转换导致的。

unsignedinta=1;signedintb=-2;if(a+b0)puts("-1islargerthan0");//==============================================unsignedshorta=1;signedshortb=-2;if(a+b0)puts("-1islargerthan0");//willnotprint

整型提升和寻常算术转换涉及到整型的秩(优先级),规则如下:

所有有符号整型的优先级都不同,即使宽度相同。

假如和short宽度相同,但的秩大于。

有符号整型的秩大于宽度比它小的有符号整型的秩

宽度为64比特,宽度为32比特,的秩更大

的秩大于,的秩大于,的秩大于signedchar

无符号整型的秩等于对应的有符号整型

unsignedint的秩等于对应的

的秩等于unsigedchar和

标准整型的秩大于等于对应宽度的拓展整型

_Bool的秩小于所有标准整型

枚举类型的秩等于对应整型

上面的规则看似复杂,但其实就是说:内置整型是一等公民,拓展整型是二等公民,是弟弟,枚举等同于整型。

整型提升的定义如下:

C.3.1.1

Ifancanrepresentallvaluesoftheoriginaltype(asrestrictedbythewidth,forabit-field),thevalueisconvertedtoan;otherwise,itisconvertedtoan.Thesearecalledtheintegerpromotions.

在算术运算中,秩小于等于和的整型(把它叫做小整型),如、等转换为或,如果可以表示该类型的全部值,则转换为,否则转换为。由于在x86等平台上,int一定可以表示这些小整型的值,因此不论是有符号还是无符号,小整型都会隐式地转换为int,不存在例外(otherwise所说的情况)。

在某些平台上,可能和一样宽。这种情况下,无法表示unsignedshort的全部值,所以要提升为。这也就是标准中说的“否则,它将转换为”。

转载请注明:
http://www.wanruiguanye.com/bzsx/77435586.html
  • 上一篇文章:

  • 下一篇文章: 没有了
  • 网站首页 版权信息 发布优势 合作伙伴 隐私保护 服务条款 网站地图 网站简介

    温馨提示:本站信息不能作为诊断和医疗依据
    版权所有 2014-2024
    今天是: