如何强制C将变量交错为有符号或无符号值?

时间:2019-06-19 19:55:23

标签: c signed

我正在一个项目中,我经常需要将某些变量解释为有符号或无符号值,并对它们进行带符号的操作;但是,在许多情况下,微妙的,看似微不足道的更改将无符号的解释替换为有符号的解释,而在其他情况下,我不能强迫C将其解释为有符号的值,并且它仍然是无符号的。这是两个示例:

int32_t pop();

//Version 1
push((int32_t)( (-1) * (pop() - pop()) ) );

//Version 2
int32_t temp1 = pop();
int32_t temp2 = pop();
push((int32_t)( (-1) * (temp1 - temp2) ) );

/*Another example */

//Version 1
int32_t get_signed_argument(uint8_t* argument) {
  return (int32_t)( (((int32_t)argument[0] << 8) & (int32_t)0x0000ff00 | (((int32_t)argument[1]) & (int32_t)0x000000ff) );
}

//Version 2
int16_t get_signed_argument(uint8_t* argument) {
  return (int16_t)( (((int16_t)argument[0] << 8) & (int16_t)0xff00 | (((int16_t)argument[1]) & (int16_t)0x00ff) );
}

在第一个示例中,版本1似乎没有将值乘以-1,而版本2却将其乘以-1,但是唯一的区别是在一种情况下将计算的中间值存储在临时变量中,或者在另一种情况下不这样做

在第二个示例中,版本1返回的值是对与版本2的返回值相同的字节的无符号解释,后者以2的补码形式对其进行解释。唯一的区别是使用int16_t或int32_t。

在两种情况下,我都使用带符号的类型(int32_t,int16_t),但这似乎不足以将它们解释为带符号的值。您能解释一下为什么这些差异导致签名差异吗?在哪里可以找到更多信息?如何使用第一个示例的简短版本,但仍然获得带符号的值?预先谢谢你!

5 个答案:

答案 0 :(得分:1)

我假设pop()返回一个无符号类型。如果是这样,表达式pop() - pop()将使用无符号算法执行,该算法是模块化的,并且如果第二个pop()大于第一个{BTW,C不会指定特定的求值顺序,因此无法保证哪个弹出值是第一个或第二个。

因此,您乘以-1的值可能不是您期望的差;如果有回绕,则可能是一个较大的正值,而不是负值。

如果直接转换至少一个函数调用,则可以获得与临时对象相当的效果。

push(-1 * ((int32_t)pop() - pop()));

答案 1 :(得分:1)

如果您只想将二进制缓冲区转换为较长的有符号整数,例如从某处收到的形式(我假设为小端)

int16_t bufftoInt16(const uint8_t *buff)
{
    return (uint16_t)buff[0] | ((uint16_t)buff[1] << 8);
}

int32_t bufftoInt32(const uint8_t *buff)
{
    return (uint32_t)buff[0] | ((uint32_t)buff[1] << 8) | ((uint32_t)buff[2] << 16) | ((uint32_t)buff[3] << 24) ;
}

int32_t bufftoInt32_2bytes(const uint8_t *buff)
{
    int16_t result = (uint16_t)buff[0] | ((uint16_t)buff[1] << 8);
    return result;
}


int main()
{
    int16_t x = -5;
    int32_t y = -10;
    int16_t w = -5567;

    printf("%hd %d %d\n", bufftoInt16(&x), bufftoInt32(&y), bufftoInt32_2bytes(&w));

    return 0;
}

将字节转换为有符号整数的方式与无符号移位完全不同。

答案 2 :(得分:0)

C语言中表达式的结果的类型取决于该表达式的组件操作数的类型,而不取决于可应用于该结果的任何强制转换。正如Barmar上面所说,要强制结果类型,您必须强制转换其中一个操作数。

答案 3 :(得分:0)

  

我正在一个项目中,我经常需要将某些变量解释为有符号或无符号值,并对它们进行有符号运算。

这似乎很烦。我的意思是说,您想在不同情况下将对象的表示形式重新解释为具有不同的类型(仅在签名形式上有所不同),或者您想要像在重新解释对象表示形式那样转换值。这种事情通常会造成混乱,但是如果您足够小心的话,您可以处理它。如果您愿意依赖实现的细节(例如,各种类型的表示形式),则可能会更容易。

在这种情况下,当务之急是要了解和理解所有the rules for implicit conversions,包括整数提升和通常的算术转换,以及它们在什么情况下适用。必须了解这些规则对表达式评估的影响-所有中间结果和最终结果的类型和值。

例如,就演员阵容而言,您可以期望的最好的

push((int32_t)( (-1) * (temp1 - temp2) ) );

是没有用的。如果该值不能用该类型表示,则(它是有符号整数类型)可能会引发信号,如果不是,则结果是实现定义的。但是,如果值 是可表示的,则转换不会更改它。无论如何,都不能免除将结果进一步转换为push()参数类型的可能性。

对于另一个示例,第一个示例的版本1和版本2之间的区别主要是何时转换值(但也请参见下文)。如果两者的确产生不同的结果,则得出pop()的返回类型与int32_t不同。在这种情况下,如果要将它们转换为其他类型以对它们执行操作,则实际上必须这样做。您的第2版通过将pop()结果分配给所需类型的变量来实现此目的,但是通过强制转换执行转换会更加惯用:

push((-1) * ((int32_t)pop() - (int32_t)pop()));

但是要注意,如果pop()调用的结果取决于它们的顺序(例如,如果它们将元素从堆栈中弹出),那么您将面临另一个问题:这些元素的相对顺序未指定要评估的操作数,因此您不能安全地假定它是一致的。出于 的原因,而不是出于打字考虑,这里最好使用版本2。

但是,总的来说,如果您有一个堆栈,其元素可能表示不同类型的值,那么我建议使元素类型成为联合(如果每个元素的类型是从上下文隐式的)或带标记的联合(如果元素需要携带有关其自身类型的信息。例如,

union integer {
    int32_t signed;
    uint32_t unsigned;
};

union integer pop();
void push(union integer i);

union integer first = pop();
union integer second = pop();
push((union integer) { .signed = second.signed - first.signed });

答案 4 :(得分:-1)

为帮助您了解代码中发生的事情,我提供了标准文本,解释了如何进行自动类型转换(对于整数),以及有关按位移位的部分,因为这部分工作原理有所不同。然后,我逐步浏览您的代码,以查看每次操作后究竟存在哪些中间类型。

标准的相关部分

6.3.1.1布尔值,字符和整数

  
      
  1. 如果一个int可以代表原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些称为整数促销。整数促销未更改所有其他类型。
  2.   

6.3.1.8常规算术转换

(我只是在这里总结相关部分。)

  1. 完成整数提升。
  2. 如果它们都已签名或都未签名,则它们都将转换为较大的类型。
  3. 如果无符号类型较大,则有符号类型将转换为无符号类型。
  4. 如果有符号类型可以代表无符号类型的所有值,则无符号类型将转换为有符号类型。
  5. 否则,它们都将转换为与签名类型大小相同的无符号类型。

(基本上,如果您有a OP b,则使用的类型的大小将是int,type(a),type(b)以及 将更喜欢可以表示可由type(a)和type(b)表示的所有值的类型。最后,它支持带符号的类型。 在大多数情况下,这将是整数。)

6.5.7按位移位运算符

  
      
  1. E1 << E2的结果是E1左移E2位位置;空位用零填充。如果E1具有无符号类型,则结果的值为$ E1 x 2 ^ {E2} $,以比结果类型可表示的最大值大的模数减少。如果E1具有带符号的类型和非负值,并且$ E1 x 2 ^ {E2} $在结果类型中是可表示的,则这是结果值;否则,行为是不确定的。
  2.   

所有这些都适用于您的代码。

由于我不知道pop()返回的类型,所以我现在跳过第一个示例。如果您将该信息添加到您的 问题,我也可以解决这个例子。

让我们逐步了解一下此表达式中发生的情况(请注意,在您的版本中进行第一次强制转换后,您还有一个额外的(;我已删除了它):

(((int32_t)argument[0] << 8) & (int32_t)0x0000ff00 | (((int32_t)argument[1]) & (int32_t)0x000000ff) )

其中一些转换取决于类型的相对大小。 令INT_TYPE等于系统中int32_t和int中的较大者。

((int32_t)argument[0] << 8)

  1. 参数[0]被显式转换为int32_t
  2. 8已经是一个整数,因此不会发生转换
  3. (int32_t)参数[0]转换为INT_TYPE。
  4. 发生左移,结果类型为INT_TYPE。

(请注意,如果参数[0]可能为负,则该移位将是未定义的行为。但是由于该移位最初是未签名的,所以在这里是安全的。)

a代表这些步骤的结果。

a & (int32_t)0x0000ff00

  1. 0x000ff0被显式转换为int32_t。
  2. 通常的算术转换。双方都转换为INT_TYPE。结果的类型为INT_TYPE。

b代表这些步骤的结果。

(((int32_t)argument[1]) & (int32_t)0x000000ff)

  1. 这两个明确的强制转换都发生了
  2. 通常的算术转换已完成。双方现在都是INT_TYPE。
  3. 结果的类型为INT_TYPE。

c代表那个结果。

b | c

  1. 通常的算术转换;因为它们都是INT_TYPE,所以没有变化。
  2. 结果的类型为INT_TYPE。

结论

因此,此处没有中间结果未签名。 (此外,大多数显式强制转换都是不必要的,尤其是在系统上sizeof(int) >= sizeof(int32_t)的情况下)。

此外,由于您从uint8_t开始,永远不要移位超过8位,并且以至少32位的类型存储所有中间结果,因此前16位将始终为0,并且值将全部为非负数,这意味着有符号和无符号类型表示您在此处可能具有的所有值完全相同

您究竟观察到什么,使您认为它使用的是无符号类型,而应该使用带符号的类型呢?我们可以看到示例输入和输出以及您期望的输出吗?

编辑: 根据您的评论,看来它无法按预期方式运行的原因不是因为类型为 unsigned ,而是因为您生成的是16位带符号整数的按位表示形式,但需要存储它们以32位带符号的整数表示。摆脱(int32_t)argument[0]之外的所有类型转换(并将其更改为(int)argument[0]int通常是系统最有效地运行的大小,因此您要使用的操作除非您有特殊原因要使用其他大小,否则为int)。然后将最终结果转换为int16_t