为什么在我的情况下不进行整数提升?

时间:2019-12-26 02:17:45

标签: c integer-promotion

#include <stdio.h>

void main()
{
    unsigned char a = 0xfb;
    char b = 0xfb;
    printf("case1 : %d, %d", a, b); // case1

    char x=90, y=60, z=100, h;
    h = x*y/z;
    printf("\ncase2 : %d", h);      // case2

    int m = 32768, n = 65536, o = 2, i;
    i = m*n/o;
    printf("\ncase3 : %d", i);      // case3
}

result

case1 : 251, -5
case2 : 54
case3 : -107341824

在情况1中,标识符b被编译为-5,这不是语法错误,因为char仅接受-128〜127的值。那么,第一个问题是标识符b在转换结束之前是否首先保存为int数据类型?(转换结束时,b将保存在char中。)

在情况2中,x,y被提升为int。因此,h具有正确的结果值。但是在case3中,m,n不会被提升为unsigned int(也许)。标识符I没有普通值(2 ^ 30)。 C99表示

  

如果一个int可以代表原始类型的所有值,则该值将转换为int;   否则,它将转换为无符号整数。

基于C99,h的值很自然,但是处理m * n / o却溢出了。这是不自然的,因为它与C99相对。这是我的第二个查询。

3 个答案:

答案 0 :(得分:4)

在情况1中,初始化在平台上签名的char且值0xfb大于CHAR_MAX的{​​{1}}并不是语法错误。效果由实现定义,毫不奇怪,0xfb被转换为具有相同8位模式的值-5。当ab作为变量参数传递给int时,将提升为printf,因为在这种情况下这是指定的行为。

在情况2中,xyz被提升为int,因为在您的平台上,类型int可以表示类型{ {1}}。使用char算术执行计算,得出int的结果,其结果在54类型的范围内,因此可以毫无问题地存储到char中。如上所述,将h传递给h时,将提升为int

在第3种情况下,printfm的类型已经为n,因此不会升级。升级规则适用于每个操作数,而不适用于以任意精度执行的操作结果:int是用m*n算术执行的,并在您的平台上溢出,其中int是大概是32位宽。行为是未定义。碰巧结果是int,因此将其除以-2147483648会得到2,但这不是C标准所保证的。

为使计算准确进行,至少-107341824m必须具有较大的类型或将其强制转换为:n将在您的平台上产生i = (unsigned)m * n / o; ,但请注意,类型107341824的位数可能少于32位。对于可移植表达式,您需要对类型int和类型unsigned long使用i类型,将其指定为至少具有32个值位。

最后,main应该定义为int main()int main(void)int main(int argc, char *argv[])或兼容的原型。 void main()不正确。

答案 1 :(得分:4)

  

在情况1中,标识符b编译为-5,这不是语法错误   以为char只接受-128〜127的值。所以,第一个问题是   标识符b首先在其末尾另存为int数据类型   翻译?(翻译结束时,b将保存在char中。)

在情况1中,变量b的初始化程序(常数0xfb)表示类型为int的值,值为251(十进制)。在C抽象机模型中,该值在初始化程序运行时转换为b的类型(char),对于块范围变量而言,该值是在执行到达该声明时(而不是在转换期间) 。如果确实在实现中char的范围是-128-127,则您已经签名了char,它们不能代表初始化程序的值,从而导致实现定义的行为。

因此,再次引用抽象机器模型,在翻译结束时, 什么都没有存储在b中。它是一个块范围变量,因此在执行达到其声明之前不存在。转换后的程序确实需要某种方式来存储b的初始值,但是C并未指定应以哪种形式存储它。但是,在翻译或执行期间,b绝对不会包含类型int的值。

在评估printf调用的参数时,将读取b的值,然后将其转换为int(因为它是可变参数)。那么可以争辩地说,使用%d字段来打印它是可以的,但是如果您要确定要打印转换前的值,则应该改为使用%hhd(尽管在您的情况下,几乎可以肯定会显示相同的结果。

  

在情况2中,x,y被提升为int。因此h具有正确的结果值。

更具体地说,在情况2中,在期间, xyz的值被提升为int对表达式x*y/z 的求值,每个操作都会产生一个int结果。乘法不会溢出所选类型int,并且总结果在类型char的范围内,因此在分配给char时应用到h的转换是没什么。

  

但是在   case3,m,n不提升为unsigned int(也许)。标识符I   没有普通值(2 ^ 30)。

在情况3中,mno已经具有类型int,因此它们不被提升,并且算术表达式会计算相同类型的结果(int)。子表达式m*n的结果不在类型int的范围内,因此,根据标准paragraph 6.5/5,出现了不确定的行为:

  

如果评估过程中发生异常情况   表达式(也就是说,如果结果没有在数学上定义,或者   不在其类型的可表示值的范围内)   未定义。

确实是

  

C99 [和C11]表示

     
    

如果一个int可以代表原始类型的所有值,则该值将转换为int;否则,它将转换为无符号整数。

  

,但这与这里无关。它是“整数提升”描述的一部分,该描述基于表达式的类型适用于表达式的操作数

  

基于C99,h的值很自然,但是处理m * n / o是   溢出。这是不自然的,因为它与C99相对。这是我的   第二个查询。

您似乎希望中间表达式m*n会被计算为产生类型unsigned int的结果,因此它不会溢出,但是标准不支持。通常的算术转换(包括整数提升)仅基于操作数类型的特征,包括其有符号性和值范围。进行常规算术转换(包括*)的运算符使用它们来确定所有操作数和结果的通用类型。

您的mn已经是同一类型,并且该类型是int,因此没有转化/促销活动。如果intm的值不等于未乘积(作为整数),则乘法结果也将是n。但是,实际上,该操作会溢出类型int,从而产生未定义的行为。

答案 2 :(得分:0)

#include <stdio.h>

int main()
{
    unsigned char a = 0xfb;
    char b = 0xfb;
    printf("case1 : %d, %d", a, b); // case1

    char x=90, y=60, z=100, h;
    h = x*y/z;
    printf("\ncase2 : %d", h);      // case2

    unsigned long long int  m = 32768, n = 65536, o = 2,i;
    i = m*n/o;
    printf("\ncase3 : %llu", i);      // case3

}

在这种类型下,您可以获得答案[0,+18,446,744,073,709,551,615]范围