C 中整数提升的问题

Problems with integer promotion in C

我正在使用 Keil 软件为 ARM 处理器 (LPC54628) 的嵌入式应用程序开发 C 代码。有一种我无法解决的奇怪行为。我在软件模拟器和微控制器上试过 运行 这个,行为是一样的。问题在于第二个 'else if' 条件的执行。

工作代码:

uint8_t a; uint8_t b ; uint8_t temp1; uint8_t temp2; uint8_t c;

a = 0x1;  b = 0x80;   temp1 = 0;  temp2 = 0;  c = 10U;
temp1 = (b << 1);  // after execution, temp1 is 0x00
temp2 = (b >> 7);  // after execution, temp2 is 0x01
__NOP();
temp1 = ((b << 1) | (b >> 7)); // after execution, temp1 is 0x00 | 0x01 = 0x01

if (a == b) {  }                                                     
else if ( a == ((b >> 1) | (b << 7)) ) {c += 1; }  
else if ( a == temp1 ) {c -= 1; } //  this 'else if' executes since a= 0x01 and temp1 = 0x01
else if ( a == ((b >> 2) | (b << 6)) ) {c += 2; } 
else if ( a == ((b << 2) | (b >> 6)) ) {c -= 2; }
else if ( a == ((b >> 3) | (b << 5)) ) {c += 3; } 
else if ( a == ((b << 3) | (b >> 5)) ) {c -= 3; }

然而,在上面的代码中有效的'else if'在下面的代码中无法执行。请注意,我所做的唯一更改是将 temp1 替换为 'else if' 条件中的实际表达式。没有其他变化。

无效代码:

a = 0x1;  b = 0x80;   temp1 = 0;  temp2 = 0;  c = 10U;
temp1 = (b << 1);  // after execution, temp1 is 0x00
temp2 = (b >> 7);  // after execution, temp2 is 0x01
__NOP();
temp1 = ((b << 1) | (b >> 7)); // after execution, temp1 is 0x00 | 0x01 = 0x01

if (a == b) {  }                                                     
else if ( a == ((b >> 1) | (b << 7)) ) {c += 1; }  
else if ( a == ((b << 1) | (b >> 7)) ) {c -= 1; } // this 'else if' DOES NOT execute.
else if ( a == ((b >> 2) | (b << 6)) ) {c += 2; } 
else if ( a == ((b << 2) | (b >> 6)) ) {c -= 2; }
else if ( a == ((b >> 3) | (b << 5)) ) {c += 3; } 
else if ( a == ((b << 3) | (b >> 5)) ) {c -= 3; }

你能指出我做错了什么吗?

整数提升很烦人。你基本上在做:

else if ( (int) a == (((int)(b << 1)) | ((int)(b >> 7))) ) {
  c -= 1;
}

这意味着您正在测试 0x01 == 0x101,但它没有。

当您执行以下操作时:

uint8_t x = 3;
uint8_t y = x + 4;

你真的在做这样的事情:

uint8_t x = 3;
uint8_t y = (uint8_t)((int) x) + 4)

在表达式((b << 1) | (b >> 7))中,值b首先被提升为类型int,因为它的类型小于[=14] =].所以这个表达式最终是:

((0x80 << 1) | (0x80 >> 7)) == (0x100 | 0x1) == 0x101

当您将此值分配给 temp1 时,它会 转换 为适合的值,剩下 0x1。当您将此表达式的结果直接与 a 进行比较时,您将值 0x1 与 0x101.

进行比较

如果您希望此表达式的结果为 8 位,则需要将其转换为 uint8_t 以截断高位。

if (a == b) {  }                                                     
else if ( a == (uint8_t)((b >> 1) | (b << 7)) ) {c += 1; }  
else if ( a == (uint8_t)((b << 1) | (b >> 7)) ) {c -= 1; }
else if ( a == (uint8_t)((b >> 2) | (b << 6)) ) {c += 2; } 
else if ( a == (uint8_t)((b << 2) | (b >> 6)) ) {c -= 2; }
else if ( a == (uint8_t)((b >> 3) | (b << 5)) ) {c += 3; } 
else if ( a == (uint8_t)((b << 3) | (b >> 5)) ) {c -= 3; }

C 编译器以前不这样做,我不知道它是什么时候改变的。

unsigned int fun0 ( unsigned char a, unsigned char b )
{
    return((a<<1)|(b>>1));
}
unsigned int fun1 ( unsigned char a, unsigned char b )
{
    return(unsigned char)((a<<1)|(b>>1));
}
00000000 <fun0>:
   0:   e1a010a1    lsr r1, r1, #1
   4:   e1810080    orr r0, r1, r0, lsl #1
   8:   e12fff1e    bx  lr

0000000c <fun1>:
   c:   e1a010a1    lsr r1, r1, #1
  10:   e1810080    orr r0, r1, r0, lsl #1
  14:   e20000ff    and r0, r0, #255    ; 0xff
  18:   e12fff1e    bx  lr

第一个运算是对8位值进行运算,然后合并返回。第二个被剪掉了。

我在很多很多年前就遇到了一年中的某一天的问题,这个错误会在当年晚些时候出现(恰好是第 256 天)并在一月一日自行修复... day = (high_byte<<8)|(low_byte); (固定为 ...((unsigned int)high_byte)<<8...)

unsigned int fun ( unsigned char a, unsigned char b )
{
    return((a<<8)|b);
}
00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

今天不会坏了...至少在 gcc 10.x.x...我也想说在某些时候它是实现定义的,但从网上的许多不同引述看来从 C99 开始就是这样...

注意反汇编是你的朋友......但是总是明白有时它是实现定义的(在这种情况下似乎不是这样)并且仅仅因为你的编译器以一种方式完成它并不意味着它是标准的并且对于所有编译器都是如此。 (例如,您使用的是 Kiel,我使用的是 gnu)。

人们 运行 非常喜欢浮点数

float fun0 ( float a, float b )
{
    return(a*(b+2.0));
}
float fun1 ( float a, float b )
{
    return(a*(b+2.0F));
}

00000000 <fun0>:
   0:   e92d4070    push    {r4, r5, r6, lr}
   4:   e1a06000    mov r6, r0
   8:   e1a00001    mov r0, r1
   c:   ebfffffe    bl  0 <__aeabi_f2d>
  10:   e3a02000    mov r2, #0
  14:   e3a03101    mov r3, #1073741824 ; 0x40000000
  18:   ebfffffe    bl  0 <__aeabi_dadd>
  1c:   e1a04000    mov r4, r0
  20:   e1a00006    mov r0, r6
  24:   e1a05001    mov r5, r1
  28:   ebfffffe    bl  0 <__aeabi_f2d>
  2c:   e1a02000    mov r2, r0
  30:   e1a03001    mov r3, r1
  34:   e1a00004    mov r0, r4
  38:   e1a01005    mov r1, r5
  3c:   ebfffffe    bl  0 <__aeabi_dmul>
  40:   ebfffffe    bl  0 <__aeabi_d2f>
  44:   e8bd4070    pop {r4, r5, r6, lr}
  48:   e12fff1e    bx  lr

0000004c <fun1>:
  4c:   e92d4010    push    {r4, lr}
  50:   e1a04000    mov r4, r0
  54:   e1a00001    mov r0, r1
  58:   e3a01101    mov r1, #1073741824 ; 0x40000000
  5c:   ebfffffe    bl  0 <__aeabi_fadd>
  60:   e1a01004    mov r1, r4
  64:   ebfffffe    bl  0 <__aeabi_fmul>
  68:   e8bd4010    pop {r4, lr}
  6c:   e12fff1e    bx  lr

2.0在编译器眼中是double,而2.0F是single。双加单运算被提升为双运算。不是整数提升,而是常量具有隐含类型(整数或浮点数)并且参与提升。