我知道这个问题已被提出并且似乎已经回答了无数次,但我似乎无法将答案与我自己的经验相匹配。
C标准规定,对于加法“两个操作数应具有算术类型”(6.5.6.1)。 Arithemitc类型包括整数和浮点类型(6.2.5.18),最后整数类型是char,short,int,long和long long,它们以有符号和无符号类型(6.2.5.4和6.2.5.6)存在。根据通常算术转换的规则“如果两个操作数具有相同的类型,则不需要进一步转换。”到目前为止一切都很好。
我的理解,如“C Book”中所示,“[n] o算术由C以比int短的精度完成”,这是应用积分推广的地方。我似乎没有在标准中找到这方面的参考,我似乎已经多次看过这个。
由于unsigned char是一种算术类型,并且通常算术转换的规则表明同一类型的操作数不需要转换,为什么需要整体提升?
我使用两个不同的编译器对此进行了测试。我写了一个简单的程序,它添加了char:
unsigned char a = 1;
unsigned char b = 2;
unsigned char c = a + b;
目标平台是使用8位架构的Atmel Mega8 uC。因此,如果操作数应该进行整体提升,则整数加法将需要使用两个寄存器。
使用imagecraft avr编译器进行编译,没有优化,并且启用了严格的ANSI C可移植性选项,产生了这个汇编代码:
mov R16, R20
add R16, R18
使用avr-gcc(我不知道类似于gcc的-strict的ANSI开关):
$ avr-gcc -O0 -mmcu=atmega8 -S -c main.c
生成的程序集:
ldd r25,Y+1
ldd r24,Y+2
add r24,r25
std Y+3,r24
两种情况下的结果代码都在一个字节上运行。我得到类似的结果|和&和逻辑||和&&这是否意味着该标准允许对charecter类型进行算术运算而不进行整数提升,或者仅仅意味着这些编译器不是标准的兼容性?
其他:
原来这一切都取决于存储结果的类型。上面显示的示例仅在结果存储在char中时才为真,并且它不依赖于添加的结果。将a设置为0xFF,将b设置为1将生成完全相同的汇编代码。
如果c的类型更改为unsigned int,则生成的程序集如下所示:
mov R2,R20
clr R3
mov R16,R18
clr R17
add R16,R2
adc R17,R3
即使在结果可以保持在单个字节中的情况下,即a = 1且b = 2。
答案 0 :(得分:6)
C 2011(n1570)6.3.1.8(“常用算术转换”)1表示在之前执行整数提升,考虑类型是否相同:
否则,将对两个操作数执行整数提升。然后将以下规则应用于提升的操作数:
如果两个操作数具有相同的类型,则不需要进一步转换......
因此,在C抽象机器中,必须在执行算术之前将unsigned char
值提升为int
。 (unsigned char
和int
具有相同大小的反常机器有一个例外。在这种情况下,unsigned char
值会提升为unsigned int
而不是int
这是深奥的,在正常情况下无需考虑。)
在实际的机器中,操作必须以与在抽象机器中执行相同的结果的方式执行。因为只有结果很重要,所以实际的中间操作不需要与抽象机完全匹配。
如果将两个unsigned char
值的总和分配给unsigned char
对象,则总和将转换为unsigned char
。这种转换实质上丢弃了超出适合unsigned char
的位的位。
这意味着C实现会得到相同的结果:
int
。int
算术添加值。unsigned char
。或者这个:
unsigned char
算术添加值。因为结果相同,所以C实现可以使用任何一种方法。
为了进行比较,我们可以考虑使用此语句:int c = a + b;
。另外,假设编译器不知道a
和b
的值。在这种情况下,使用unsigned char
算术进行添加可能会产生与将值转换为int
并使用int
算术不同的结果。例如,如果a
为250且b
为200,那么它们的总和为unsigned char
值为194(250 + 200%256),但它们在int
算术中的总和为450.因为存在差异,C实现必须使用获得正确总和的指令,450。
(如果编译器确实知道a
和b
的值,或者可以证明总和适合unsigned char
,那么编译器可以再次使用unsigned char
的运算。)
答案 1 :(得分:4)
以下是C99的相关部分:
6.3.1算术操作数
6.3.1.1布尔,字符和整数
1每个整数类型都有一个整数转换等级,定义如下:
...
2如果可以使用int或unsigned int,则可以在表达式中使用以下内容:
- 具有整数类型的对象或表达式,其整数转换等级小于int和unsigned int的等级 - _Bool,int,signed int或unsigned int类型的位字段 如果int可以表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些被称为整数促销。所有其他类型都不会被整数促销更改。
我同意这是模糊不清的,但这是你能找到的最接近w.r.t.将各种char
或short
或_Bool
转换为int
或unsigned int
。
来自同一来源:
5.1.2.3程序执行
在抽象机器中,所有表达式都按语义指定进行计算。实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括由调用函数或访问易失性对象引起的任何副作用)。
...
10例2执行片段
char c1,c2;
/ * ...... * /
c1 = c1 + c2;
''整数促销''要求抽象机器将每个变量的值提升为int大小,然后添加两个整数并截断总和。如果可以在没有溢出的情况下添加两个字符,或者通过静默溢出来生成正确的结果,则实际执行只需要产生相同的结果,可能省略促销。
答案 2 :(得分:2)
在6.3.1.8(通常的算术转换,n1570)中,我们可以阅读
否则,将对两个操作数执行整数提升。然后将以下规则应用于提升的操作数:
所以整数提升是整数类型的通常算术转换的一部分。
因此,在abstratc机器中,必须完成到(unsigned) int
的转换。
但是,通过“似乎”规则,如果行为与严格实现抽象机器的行为无法区分,那么实现可能会以不同的方式做事。
因此,如果保证仅使用单个字节的计算与促进int
的计算具有相同的结果,则允许实现使用单字节算法。
答案 3 :(得分:1)
如果计算机能够对小于int
的类型执行操作,那么无论如何标准都永远不会阻止它。请记住,标准会尝试为编译器提供尽可能多的选项,并决定选择最适合他们的方法。
短语“没有算术由C以比int短的精度完成也是正确的。如果你密切注意,你会发现算术确实以不小于int
的精度完成。但这并不意味着编译器被强制执行整数提升,因为它可以在示例程序中安全地执行字节操作并获得相同的精度。
答案 4 :(得分:0)
我认为这里没有矛盾。只要可观察的结果就好像它将按照规定的方式进行,编译器就没有义务遵循任何特定的计算路径。
特别是对于你的情况,如果我们用升级到int(比如16位)进行计算:a
提升为int
具有相同的值,{{1也是。 b
的值实际上是a + b
,但是我们将它分配给unsigned char,它截取高8位,这与取结果(a + b) mod 2^16
相同:{{1 }}
没有整数提升的计算会产生mod 2^8
,这是完全相同的。