符号扩展什么时候完成?

when is sign extension done?

我正在为 MSP430 编译 C。 我想知道当对字符(或(u)int8_t)进行符号扩展到寄存器大小(16 位)时的具体规则是什么我发现符号扩展将在目标的 MSB 时完成操作数将影响指令和所有后续指令的正确结果。 但是,我认为这并不能真正解释它。例如,当查看此代码时:

#include <stdint.h>

unsigned char uscfun ( signed char a, signed char b )
{
    return(a+b)*2;
}

signed char scfun (  char a, signed char b )
{
   return(a+b)*2;
}

signed char scufun ( char a, char b )
{
   return(a+b)<<1;
}

int8_t sint8ufun (  uint8_t a,  int8_t b )
{
   return(a+b)*3;
}

int8_t sint8uufun (  uint8_t a,  uint8_t b )
{
    int8_t new = (a + b)*3;
   //return(a+b)<<1;
   return new;
}

int8_t sint8fun (  int8_t a,  int8_t b )
{
   return(a+b)*2;
}

    
    uint8_t usint8fun (int8_t a, int8_t b)
{
    return((a+b)<<2);
}

uint8_t usint8fun2 (int8_t a, int8_t b)
{
    return(a+b)*4;
}

将给出以下程序集(用-O3编译)

    uscfun:
      SXT R12
      SXT R13
      ADD.W R13, R12
      RLA.W R12
      RET
    scfun:
      AND #0xff, R12
      SXT R13
      ADD.W R13, R12
      RLA.W R12
      RET
    scufun:
      AND #0xff, R12
      AND #0xff, R13
      ADD.W R13, R12
      RLA.W R12
      RET
    sint8ufun:
      AND #0xff, R12
      SXT R13
      ADD.W R12, R13
      MOV.B R13, R12
      ADD.B R13, R12
      ADD.B R13, R12
      RET
    sint8uufun:
      ADD.B R13, R12
      MOV.B R12, R13
      ADD.B R12, R13
      ADD.B R13, R12
      RET
    sint8fun:
      SXT R12
      SXT R13
      ADD.W R13, R12
      RLA.W R12
      RET
    usint8fun:
        ADD.B   R13, R12
        rpt     #2 { rlax.w       R12
        RET
usint8fun2:
        SXT     R12
        SXT     R13
        ADD.W   R13, R12
        rpt     #2 { rlax.w       R12
        RET

起初我以为SXT只有在它的return值被签名时才会完成。然而,正如最后一种方法所示,情况不一定如此。我真的不明白为什么。

其次,位移位似乎并不总是有助于符号扩展。我本以为会这样,因为这显然会影响操作数的 MSB。更好的是,最后一种方法两次显示相同的指令,只是写下不同(*2 与 <<1 相同),一个需要符号扩展,另一个不需要。

我知道减法和加法不需要 SXT,但我真的不知道它什么时候适用于移位、乘法和除法,什么时候不需要。

问题基本上是:使用符号扩展时是否有一套明确的规则?

编辑:上面给出的例子只是一些例子,表明符号扩展以多种方式使用,以致于我并不真正理解给定规则在使用时的含义。

设 f(a, b,…) 是有符号对象的函数 a, b,… 让 a', b',… 成为重新解释的那些对象的值作为未签名。如果,对于 ab、…(在它们的符号类型中)的所有值,f(a, b,…) 作为无符号等于 f(a', b', …), 那么在计算 f.

时不需要 a, b,… 的符号扩展

这是显而易见的,因为如果满足条件,则 f(a', b',…) 产生位需要表示 f(a, b,…)。但是,它可能不完整。我们可能有一些 f(a, b) 需要符号扩展 a 但不是b,上面没有直接解决。但是可以认为包含在f(a,b)可以表示为函数ga(b),然后上面告诉我们不需要b的符号扩展在评估 ga 时。如果这对所有 ga 都成立,则计算 f(a, b) 不需要 b.

的符号扩展

此外,不需要符号扩展这一事实并不意味着编译器一定会检测到这一点并生成没有符号扩展的代码。即使没有必要,编译器也可能会生成符号扩展。我想这可能会在sint8fun中看到;我希望 (int8_t) (a+b)*2 可以作为加法和没有符号扩展的转变来评估。但是,编译器可能无法解释表达式被 return 转换为 int8_t 的事实。 (a+b)*2 本身确实需要符号扩展,因为它可能会产生负面的 int 结果,如果不扩展符号则不会。只有在转换为 int8_t 之后,结果才与符号扩展无关。

The question basically is: Is there a set of clear rules when sign extension is used?

TL;DR:因为您似乎在谈论 sign-extension 机器指令,所以答案是“不”。

符号扩展不是 C 语言的概念,因此该语言没有规定有关何时执行的规则。 C 确实有关于值 converted 到更宽类型的情况的规则,并且对于大多数 C 实现,其中一些转换原则上会涉及符号扩展,但那是不一样的事物。这尤其不是一回事,因为在大多数情况下,只要实现符合语言标准所定义的 externally-observable 行为,就允许实现对您的代码做他们想做的事。

该语言指定 signed char 和 (signed) short 类型的表达式在作为操作数出现的任何地方都转换为 (signed) int算术运算符、按位运算符、逻辑运算符和索引运算符,作为“整数提升”的一部分。 unsigned charunsigned short 在相同情况下被转换为 (signed) int 如果 int 可以表示这些类型的所有值,或者 unsigned int 否则在那些情况下。我可能忽略了其他一些情况。 普通 char 的行为要么在所有方面都像 signed char,要么在所有方面都像 unsigned char,具体取决于 C 实现。

这些隐式转换无疑是您观察编译器生成 SXT 指令趋势的统一因素。编译器可能会发出此类指令来实现所需的转换,但它没有义务这样做,因为它可以在没有的情况下获得相同的结果。