以avr-gcc为例,将int类型指定为16位宽。在C中对8位操作数执行运算会导致由于C中的整数提升而将这些操作数转换为16位int类型。这是否意味着如果用C编写,则AVR上的所有8位算术运算都将比在C中编写的时间长得多。是否由于C的整数提升而以汇编形式编写?
以avr-gcc为例,将int类型指定为16位宽。在C中对8位操作数执行运算会导致由于C中的整数提升而将这些操作数转换为16位int类型。这是否意味着如果用C编写,则AVR上的所有8位算术运算都将比在C中编写的时间长得多。是否由于C的整数提升而以汇编形式编写?
Answers:
unsigned int fun1 ( unsigned int a, unsigned int b )
{
return(a+b);
}
unsigned char fun2 ( unsigned int a, unsigned int b )
{
return(a+b);
}
unsigned int fun3 ( unsigned char a, unsigned char b )
{
return(a+b);
}
unsigned char fun4 ( unsigned char a, unsigned char b )
{
return(a+b);
}
正如预期的那样fun1都是整数,因此16位数学运算也是如此
00000000 <fun1>:
0: 86 0f add r24, r22
2: 97 1f adc r25, r23
4: 08 95 ret
尽管从技术上讲是不正确的,因为它是由代码调用的16位加法运算,但即使未优化,该编译器也会由于结果大小而删除了ADC。
00000006 <fun2>:
6: 86 0f add r24, r22
8: 08 95 ret
促销真的发生在这里并不感到惊讶,编译器以前不知道这样做是什么版本,所以在我的职业生涯初期就遇到了这个问题,尽管编译器的促销乱序(就像上面一样),尽管我做了促销告诉它做uchar数学,并不惊讶。
0000000a <fun3>:
a: 70 e0 ldi r23, 0x00 ; 0
c: 26 2f mov r18, r22
e: 37 2f mov r19, r23
10: 28 0f add r18, r24
12: 31 1d adc r19, r1
14: 82 2f mov r24, r18
16: 93 2f mov r25, r19
18: 08 95 ret
理想情况下,我知道它是8位,想要8位结果,所以我只是简单地告诉它一直执行8位。
0000001a <fun4>:
1a: 86 0f add r24, r22
1c: 08 95 ret
因此,通常最好将寄存器大小作为目标,最好是(u)int的大小,对于像这样的8位MCU,编译器作者必须做出让步...一点都不习惯使用uchar进行数学运算,您知道不需要超过8位,因为当您在具有较大寄存器的处理器上移动该代码或编写新代码时,编译器必须开始屏蔽和符号扩展,其中某些代码本来就是这样做的,和其他人不。
00000000 <fun1>:
0: e0800001 add r0, r0, r1
4: e12fff1e bx lr
00000008 <fun2>:
8: e0800001 add r0, r0, r1
c: e20000ff and r0, r0, #255 ; 0xff
10: e12fff1e bx lr
强迫8位的成本更高。我作弊了很多,需要一些更复杂的示例才能公平地看到更多内容。
根据评论讨论进行编辑
unsigned int fun ( unsigned char a, unsigned char b )
{
unsigned int c;
c = (a<<8)|b;
return(c);
}
00000000 <fun>:
0: 70 e0 ldi r23, 0x00 ; 0
2: 26 2f mov r18, r22
4: 37 2f mov r19, r23
6: 38 2b or r19, r24
8: 82 2f mov r24, r18
a: 93 2f mov r25, r19
c: 08 95 ret
00000000 <fun>:
0: e1810400 orr r0, r1, r0, lsl #8
4: e12fff1e bx lr
没有惊喜。尽管优化器为什么留下了多余的指令,但是您不能在r19上使用ldi吗?(当我问时我知道答案)。
编辑2
对于AVR
avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
避免不良习惯或不进行8位比较
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
显然,优化只需要花一秒钟的时间就可以尝试使用您自己的编译器来查看它与我的输出的比较情况,但是无论如何:
whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o
是的,使用字节作为字节大小的变量(肯定是在avr,pic等上)将节省您的内存,并且您想真正尝试保存它...如果您实际使用它,但是如此处所示,尽可能少由于要尽可能多地存储在内存中,因此要节省闪存是因为没有额外的变量,内存节省可能是真实的,也可能不是真实的。
unsigned char
因此必须按要求将其提升为16位。按标准。
(a<<8)|b
对于任何int
16位系统总是错误的。a
将隐式提升为int
已签名。如果a
MSB中保留一个值,则最终会将数据移入16位数字的符号位,这将导致未定义的行为。
不一定,因为现代编译器在优化生成的代码方面做得很好。例如,如果您将z = x + y;
所有变量都写在哪里unsigned char
,则要求编译器unsigned int
在执行计算之前将其提升为。但是,由于最终结果将完全相同而无需提升,编译器将生成仅添加8位变量的代码。
当然,情况并非总是如此,例如,结果z = (x + y)/2;
将取决于高字节,因此将进行升级。仍然可以通过不将中间结果转换回来避免组装unsigned char
。
使用编译器选项可以避免某些此类效率低下的情况。例如,许多8位编译器都有实用程序或命令行开关来将枚举类型适合1个字节,而不是int
C所要求的。
int
,因为char
很可能不会具有与int
任何平台相同的转换等级。
int
(是的,这是不一致的)。C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...