ToB企服应用市场:ToB评测及商务社交产业平台

标题: 在 ISO C90 标准中 C 语言负数比正数大? [打印本页]

作者: 宝塔山    时间: 2024-10-2 20:15
标题: 在 ISO C90 标准中 C 语言负数比正数大?
演示环境

演示代码
  1. int main(void)
  2. {
  3.         return -2147483648 < 2147483647;
  4. }
复制代码
编译和链接
  1. gcc -std=c90 -m32 main.c # 添加 -masm=intel 选项可以生成 intel 语法的汇编
复制代码
gcc 输入警告:
warning: this decimal constant is unsigned only in ISO C90
运行并查看结果
  1. ./a.out
  2. echo $?
复制代码
输出结果为:0
先看一下编译生成的汇编代码

  1. gcc -S -std=c90 -m32 main.c
复制代码
  1. main:
  2. .LFB0:
  3.         .cfi_startproc
  4.         pushl        %ebp
  5.         .cfi_def_cfa_offset 8
  6.         .cfi_offset 5, -8
  7.         movl        %esp, %ebp
  8.         .cfi_def_cfa_register 5
  9.         call        __x86.get_pc_thunk.ax
  10.         addl        $_GLOBAL_OFFSET_TABLE_, %eax
  11.         movl        $0, %eax # 11 行
  12.         popl        %ebp
  13.         .cfi_restore 5
  14.         .cfi_def_cfa 4, 4
  15.         ret
  16.         .cfi_endproc
  17. .LFE0:
复制代码
看一下第 11 行汇编指令 movl        $0, %eax,返回值 0 是 gcc 在编译阶段计算(优化)的结果(意料之中!特别是今世编译器,哪怕是在默认的优化级别下,也没来由不进行优化)。
关于 ISO C90 标准

在 ISO C90 标准中,-2147483648 是由一个负号和 2147483648 两部分组成。根据 C90 标准的规定,整数常量(不带后缀)会根据其巨细自动决定是 int 范例、long int 范例照旧 unsigned long int 范例。
2147483648 超出了 32 位 int 的最大范围(2147483647),因此编译器会把它当成 unsigned long int 范例。所以,-2147483648 其实是 - 和一个 unsigned long int 范例 2147483648 组成,由于这里的负号是尝试对一个无符号数取负,这将引发范例标题,这样的操作在 C 语言中是正当的,但会导致值的环绕(wrap around),最终得到一个很大的正数。
解决办法

既然标题出在 -2147483648 的绝对值太大了,如果将 -2147483648 改为其它的表达式形式,只要表达式的结果稳定且表达式中的每个子表达式不超出范围不就变小了吗?这里以表达式 -2147483647 - 1 进行演示:
  1. int main(void)
  2. {
  3.         return (-2147483647 - 1) < 2147483647;
  4. }
复制代码
重复之前的编译链接步骤,发现不光没有了警告,而且运行的结果也是对的。
  1. #include <limits.h>
  2. int main(void)
  3. {
  4.     return INT_MIN < INT_MAX;
  5. }
复制代码
我们知道宏是在预处理(预编译)阶段进行处理(替换)的,那么宏 INT_MIN 和 INT_MAX 会分别替换成什么呢?
利用下令 gcc -E -std=c90 -m32 main.c 预处理的结果为(只截取了关键部分):
  1. int main(void)
  2. {
  3.     return (-0x7fffffff - 1) < 0x7fffffff;
  4. }
复制代码
-0x7fffffff 和 0x7fffffff 不就分别是 -2147483647 和 2147483647 的十六进制形式吗?所以,方法 1 和 2 本质是一样的。
  1. int main(void)
  2. {
  3.         int min = -2147483648;
  4.         return min < 2147483647;
  5. }
复制代码
依然有雷同的警告,但结果(1)居然是对的?照旧先看一下编译生成的汇编代码(同样只截取关键部分):
  1. main:
  2. .LFB0:
  3.         .cfi_startproc
  4.         pushl        %ebp
  5.         .cfi_def_cfa_offset 8
  6.         .cfi_offset 5, -8
  7.         movl        %esp, %ebp
  8.         .cfi_def_cfa_register 5
  9.         subl        $16, %esp
  10.         call        __x86.get_pc_thunk.ax
  11.         addl        $_GLOBAL_OFFSET_TABLE_, %eax
  12.         movl        $-2147483648, -4(%ebp)
  13.         cmpl        $2147483647, -4(%ebp)
  14.         setne        %al
  15.         movzbl        %al, %eax
  16.         leave
  17.         .cfi_restore 5
  18.         .cfi_def_cfa 4, 4
  19.         ret
  20.         .cfi_endproc
复制代码
movl $-2147483648, -4(%ebp) 的意思是将 -2147483648 压栈,其实就是放到内存中,由于 -2147483648 是负数,在内存中存放的是补码形式(也就是十六进制的 0x80000000)。
cmpl $2147483647, -4(%ebp) 的意思是比较 2147483647(十六进制为 0x7FFFFFFF)和 0x80000000 的巨细,比较的结果存放到标志寄存器的对应位中。
setne        %al 的意思是如果比较的结果为不相称(显着 0x7FFFFFFF 和 0x80000000),将 al 寄存器置 1(二进制表示为:0000001)
movzbl 指令负责拷贝一个字节,并用 0 添补其目的操作数中的其余各位。因此,movzbl        %al, %eax 指令执行后,寄存器 eax(也就是返回值)为 0x00000001。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4