当ARM gcc 9.2.1使用命令行选项-O3 -xc++ -mcpu=cortex-m0
[以C ++编译]并使用以下代码:
unsigned short adjust(unsigned short *p)
{
unsigned short temp = *p;
temp -= temp>>15;
return temp;
}
它生成合理的机器码:
ldrh r0, [r0]
lsrs r3, r0, #15
subs r0, r0, r3
uxth r0, r0
bx lr
等同于:
unsigned short adjust(unsigned short *p)
{
unsigned r0,r3;
r0 = *p;
r3 = temp >> 15;
r0 -= r3;
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return r0; // computing a 32-bit unsigned value 0-65535.
}
很合理。在这种情况下,最后的“uxtw”实际上可以省略,但对于那些无法证明此类优化的安全性的编译器来说,更好的选择是谨慎一些,以避免返回0-65535范围外的值,这可能会完全破坏下游代码。
然而,在使用-O3 -xc -mcpu=cortex-m0
[除了编译为C而不是C ++之外,选项相同]时,代码将发生变化:
ldrh r3, [r0]
movs r2, #0
ldrsh r0, [r0, r2]
asrs r0, r0, #15
adds r0, r0, r3
uxth r0, r0
bx lr
unsigned short adjust(unsigned short *p)
{
unsigned r0,r2,r3;
r3 = *p;
r2 = 0;
r0 = ((unsigned short*)p)[r2];
r0 = ((int)r0) >> 15; // Effectively computes -((*p)>>15) with redundant load
r0 += r3
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return temp; // computing a 32-bit unsigned value 0-65535.
}
我知道在C和C++中,左移操作的定义边界不同,但我认为右移操作是相同的。在C和C++中,是否有一些关于右移操作方式的不同之处会导致编译器使用不同的代码来处理它们?在早期版本中,在C模式下生成的代码会稍微好一些(不过现在已经更新到9.2.1版本了):
ldrh r3, [r0]
sxth r0, r3
asrs r0, r0, #15
adds r0, r0, r3
uxth r0, r0
bx lr
等同于:
unsigned short adjust(unsigned short *p)
{
unsigned r0,r3;
r3 = *p;
r0 = (short)r3;
r0 = ((int)r0) >> 15; // Effectively computes -(temp>>15)
r0 += r3
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return temp; // computing a 32-bit unsigned value 0-65535.
}
相比于9.2.1版本不那么糟糕,但仍比直接翻译代码要长。在使用9.2.1时,将参数声明为unsigned short volatile *p
可以消除冗余的p
加载,但我很好奇为什么gcc 9.2.1需要一个volatile
限定符来帮助它避免冗余加载,或者为什么这种奇怪的“优化”只发生在C模式下而不是C ++模式下。我还有些好奇,为什么gcc甚至考虑添加((short)temp) >> 15
而不是减去temp >> 15
。是否有某个优化阶段会认为这是有意义的呢?
temp
被提升为一个int
进行右移,而在C语言下,temp
只被提升为一个signed short
。也许C编译版本无法像这样优化得那么好,因为这可能是C和C++之间整数提升/转换的差异引起的? - clyne-fopt-info
选项,以获取更深入的了解优化器正在做什么,而不是猜测。 - chris