代码之家  ›  专栏  ›  技术社区  ›  Lundin

隐式类型提升规则

  •  105
  • Lundin  · 技术社区  · 8 年前

    这篇文章是关于C语言中隐式整数提升的常见问题,特别是由常见的算术转换和/或整数提升引起的隐式提升。


    为什么这会给出一个奇怪的大整数而不是255?

    unsigned char x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    

    示例2)
    为什么会得出“-1大于0”?

    unsigned int a = 1;
    signed int b = -2;
    if(a + b > 0)
      puts("-1 is larger than 0");
    

    示例3)
    为什么将上面示例中的类型更改为 short 解决问题?

    unsigned short a = 1;
    signed short b = -2;
    if(a + b > 0)
      puts("-1 is larger than 0"); // will not print
    

    (这些示例适用于32位或64位计算机,短16位。)

    3 回复  |  直到 4 年前
        1
  •  142
  •   Lundin    4 年前

    不幸的是,隐式类型提升的规则弊大于利,甚至可能是C语言中最大的缺陷之一。这些规则通常连一般的C程序员都不知道,因此会导致各种各样的非常微妙的错误。

    通常你会看到程序员说“只要转换到x类型就行了”的场景,但他们不知道为什么。或者,这些bug表现为罕见的间歇性现象,从看似简单直接的代码中突现出来。隐式提升在执行位操作的代码中特别麻烦,因为C中的大多数位运算符在给定有符号操作数时都具有定义不好的行为。


    整数类型和转换秩

    C中的整数类型是 char , short , int long , long long enum .
    _Bool bool 在类型提升时,也被视为整数类型。

    所有整数都有一个指定的 转换秩

    每个整数类型都有一个整数转换秩,定义如下:
    任何两种有符号整数类型都不得具有相同的秩,即使它们具有相同的表示。
    有符号整数类型的秩应大于精度较低的任何有符号整数类型的秩。
    排名 long long int 应大于 long int ,其应大于 short int ,其应大于 signed char

    任何标准整数类型的秩应大于具有相同宽度的任何扩展整数类型的秩。
    字符的秩应等于有符号字符和无符号字符的秩。

    任何枚举类型的秩应等于兼容整数类型的秩(见6.7.2.2)。

    stdint.h 也在这里排序,与给定系统上它们恰好对应的任何类型具有相同的秩。例如 int32_t 排名与 整数 在32位系统上。

    此外,C11 6.3.1.1规定了哪些类型被视为 小整数类型 (非正式术语):

    整数 unsigned int 也许 使用:

    整数 无符号整型 整数 无符号整型

    这个有点晦涩的文本在实践中的意思是 _布尔 烧焦 (以及 int8_t , uint8_t etc)是“小整数类型”。如下文所述,这些问题以特殊方式处理,并受到隐性提升的影响。


    每当在表达式中使用小整数类型时,它都会隐式转换为 整数 它总是有签名的。这被称为 整数提升规则

    从形式上讲,该规则说(C11 6.3.1.1):

    可以表示原始类型的所有值(受宽度限制,对于位字段),该值将转换为 整数 ; 否则,它将转换为 无符号整型 整数促销

    整数 在大多数表达式中使用时。

    本文经常被误解为:“所有小的有符号整数类型都转换为有符号整数,所有小的无符号整数类型都转换为无符号整数”。这是不正确的。这里的无符号部分仅意味着,如果我们有一个 unsigned short 整数 恰好与 短的 在给定系统上,则 无符号短 . 在中,没有什么值得注意的事情发生。但以防万一 短的 是比 整数 不管是有符号还是无符号 !

    烧焦 短的 . 操作始终在

    这听起来可能是胡说八道,但幸运的是编译器可以优化代码。例如,一个表达式包含两个 unsigned char 操作数会将操作数提升为 操作如下: 整数 . 但编译器可以优化表达式,使其实际作为8位操作执行,正如预期的那样。然而,问题来了:编译器是 允许优化由整数提升引起的符号的隐式变化。因为编译器无法判断程序员是有意依赖隐式提升还是无意。

    整数 ,以及 x - y 整数 -1 255 这可能是意料之中的。编译器可以生成用8位指令执行代码的机器代码,而不是 整数 printf("%u 被调用。示例1可以通过将操作结果转换回类型来修复 无符号字符

    ++ sizeof 运算符,无论使用一元、二元(或三元)运算符,整数提升几乎适用于C中的所有运算。


    通常的艺术转换 (有时非正式地称为“平衡”)。这些在C11 6.3.18中规定:

    if-else if 语句,可能更容易阅读:))

    6.3.1.8常用算术转换

    和结果。对于指定的操作数,将转换每个操作数,而不更改类型 域,其对应的实数类型是公共实数类型。除非 结果,如果操作数相同,则其类型域为操作数的类型域,

    • 首先,如果任一操作数的对应实数类型为 long double ,在不改变类型域的情况下,将另一个操作数转换为相应实类型为的类型 长双人
    • double ,在不改变类型域的情况下,将另一个操作数转换为相应实类型为的类型
    • 否则,如果任一操作数的对应实数类型为 float ,另一个操作数在不改变类型域的情况下转换为相应实类型为浮点的类型。
    • 如果两个操作数的类型相同,则无需进一步转换。
    • 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型 整数类型,整数转换秩较小的类型的操作数为
    • 等于另一个操作数类型的秩,然后是具有 有符号整数类型转换为无符号的操作数类型 整数类型。
    • 否则,如果带符号整数类型的操作数的类型可以表示 具有无符号整数类型的操作数类型的所有值,然后 带符号整数类型的操作数。
    • 否则,两个操作数都转换为无符号整数类型

    整数

    a + b 例2中给出了一个奇怪的结果。两个操作数都是整数,并且它们至少具有秩 ,因此整数促销不适用。操作数的类型不同- a b signed int b . 在此转换过程中,它会丢失符号信息,并最终成为一个大值。

    短的 在示例3中修复了问题,因为 短的 整数 签名。整数提升后,两个操作数的类型相同( ),无需进一步转换。然后可以按预期在有符号类型上执行该操作。

        2
  •  8
  •   Gabriel Staples    4 年前

    int main(){
        unsigned char x = 0;
        unsigned char y = 1;
        printf("%u\n", x - y); 
        printf("%d\n", x - y);
    }
    

    由于无符号字符小于int,我们对其应用整数提升,因此我们得到(int)x-(int)y=(int)(-1)和无符号int(-1)=4294967295。

    上述代码的输出:(与我们预期的相同)

    4294967295
    -1
    

    我尝试了上一篇文章推荐的方法,但实际上并不奏效。 以下是基于之前帖子的代码:

    int main(){
        unsigned int x = 0;
        unsigned char y = 1;
        printf("%u\n", x - y); 
        printf("%d\n", x - y);
    }
    

    由于x已经是一个无符号整数,我们只对y应用整数提升。然后我们得到(无符号int)x-(int)y。由于它们仍然没有相同的类型,我们应用通常的算术凸,我们得到(无符号int)x-(无符号int)y=4294967295。

    上述代码的输出:(与我们预期的相同):

    4294967295
    

    类似地,以下代码得到相同的结果:

    int main(){
        unsigned char x = 0;
        unsigned int y = 1;
        printf("%u\n", x - y); 
        printf("%d\n", x - y);
    }
    

    将两者都更改为无符号int

    int main(){
        unsigned int x = 0;
        unsigned int y = 1;
        printf("%u\n", x - y); 
        printf("%d\n", x - y);
    }
    

    -1
    

    修复代码的可能方法之一:(在末尾添加类型转换)

    int main(){
        unsigned char x = 0;
        unsigned char y = 1;
        printf("%u\n", x - y); 
        printf("%d\n", x - y);
        unsigned char z = x-y;
        printf("%u\n", z);
    }
    

    4294967295
    -1
    255
    

    int main(){
        unsigned int a = 1;
        signed int b = -2;
        if(a + b > 0)
            puts("-1 is larger than 0");
            printf("%u\n", a+b);
    }
    

    由于两者都是整数,因此不需要整数提升。通过通常的算术转换,我们得到(无符号int)a+(无符号int)b=1+4294967294=4294967295。

    -1 is larger than 0
    4294967295
    

    如何修复?

    int main(){
        unsigned int a = 1;
        signed int b = -2;
        signed int c = a+b;
        if(c < 0)
            puts("-1 is smaller than 0");
            printf("%d\n", c);
    }
    

    -1 is smaller than 0
    -1
    

    示例3)

    int main(){
        unsigned short a = 1;
        signed short b = -2;
        if(a + b < 0)
            puts("-1 is smaller than 0");
            printf("%d\n", a+b);
    }
    

    最后一个示例修复了这个问题,因为a和b都由于整数提升而转换为int。

    上述代码的输出:

    如果我混淆了一些概念,请告诉我。谢谢~

        3
  •  0
  •   Gabriel Staples    4 年前

    C和C中的整数和浮点秩及提升规则++

    the main one by @Lundin . 如果你想在下面的例子之外找到更多的例子,那么也可以详细研究这个答案,同时参考我下面的“规则”和“促销流程”总结。

    我还在这里编写了自己的示例和演示代码: integer_promotion_overflow_underflow_undefined_behavior.c .

    3条简单规则

    1. 对于涉及多个操作数(输入变量)的任何操作(例如:数学运算、比较或三值),变量为 促进 根据所需变量类型 执行该操作。
      1. 因此,您必须手动、显式地 投射输出 如果你不希望它被隐式地为你选择,那么你可以将它转换为你想要的任何类型。请参见下面的示例。
    2. int int32_t int32\u t
    3. 整数

    实例

    示例:使用此代码:

    uint8_t x = 0;
    uint8_t y = 1;
    

    ...如果你这样做了 x - y 整数 (即 在我的64位 系统),您将得到以下结果: (int)x - (int)y 整数 键入值 -1 uint8_t 价值类型 255 . 获得所需的 手动 将结果返回到 uint8\u t ,通过这样做: (uint8_t)(x - y) .

    促销规则如下。晋升自 类型如下。
    阅读“ --> 作为“升职”。

    [int8_t] "fixed-width integer types" 对于典型的64位Unix(Linux或Mac)体系结构上的给定标准类型。例如,请参见:

    1. https://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)IntegerTypes.html
    2. https://www.ibm.com/docs/en/ibm-mq/7.5?topic=platforms-standard-data-types
    3. 更好的是,在你的机器上自己测试一下 stdint_sizes.c 从我的 eRCaGuy_hello_world 回购。

    注: = bool ( _Bool ), char [int8_t] , unsigned char [uint8_t] short [int16_t] , unsigned short [uint16_t] .

    小型: 布尔 _布尔 ), 字符[int8\u t] , 短[int16\u t] 无符号短[uint16\u t]
    --&燃气轮机; int [int32_t]
    unsigned int [uint32_t]
    long int [int64_t]
    unsigned long int [uint64_t]
    --&燃气轮机; long long int [int64_t]
    --&燃气轮机; unsigned long long int [uint64_t]

    void* )和 size_t 都是64位,所以我认为它们适合 uint64_t

    2.对于浮点类型

    float [32-bits] double [64-bits] long double [128-bits]