在 ISO C90 标准中 C 语言负数比正数大?
cnblogs 2024-10-03 08:09:00 阅读 74
演示环境
- <li>OS: Arch Linux x86_64
- Kernel: linux-6.11.1
- GCC: 14.2.1
演示代码(main.c)
<code>int main(void)
{
return -2147483648 < 2147483647;
}
编译和链接
gcc -std=c90 -m32 main.c #
gcc 输入警告:
main.c:3:9: warning: this decimal constant is unsigned only in ISO C90
运行并查看结果
./a.out
echo $?
输出结果为:0
先看一下编译生成的汇编代码
- <li>执行命令
gcc -S -std=c90 -m32 main.c # 添加 -masm=intel 选项可以生成 intel 语法的汇编
- <li>生成的汇编代码如下(只截取了关键部分):
<code>main:
.LFB0:
.cfi_startproc
pushl%ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl%esp, %ebp
.cfi_def_cfa_register 5
call__x86.get_pc_thunk.ax
addl$_GLOBAL_OFFSET_TABLE_, %eax
movl$0, %eax # 11 行
popl%ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.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),最终得到一个很大的正数。
解决办法
- <li>以表达式代替
-2147483648
既然问题出在 -2147483648
的绝对值太大了,如果将 -2147483648
改为其它的表达式形式,只要表达式的值不变且表达式中的每个子表达式不超出范围不就行了吗?这里以表达式 -2147483647 - 1
进行演示:
int main(void)
{
return (-2147483647 - 1) < 2147483647;
}
重复之前的编译链接步骤,发现不但没有了警告,而且运行的结果也是对的。
- <li>使用宏(推荐)
int main(void)
{
return INT_MIN < INT_MAX;
}
我们知道宏是在预处理(预编译)阶段进行处理(替换)的,那么宏 INT_MIN
和 INT_MAX
会分别替换成什么呢?
使用命令 gcc -E -std=c90 -m32 main.c
预处理的结果为(只截取了关键部分):
int main(void)
{
return (-0x7fffffff - 1) < 0x7fffffff;
}
-0x7fffffff
和 0x7fffffff
不就分别是 -2147483647
和 2147483647
的十六进制形式吗?所以,方法 1 和 2 本质是一样的。
- <li>使用变量(不推荐)
<code>int main(void)
{
int min = -2147483648;
return min < 2147483647;
}
依然有相同的警告,但结果居然是对的?还是先看一下编译生成的汇编代码(同样只截取关键部分):
main:
.LFB0:
.cfi_startproc
pushl%ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl%esp, %ebp
.cfi_def_cfa_register 5
subl$16, %esp
call__x86.get_pc_thunk.ax
addl$_GLOBAL_OFFSET_TABLE_, %eax
movl$-2147483648, -4(%ebp)
cmpl$2147483647, -4(%ebp)
setne%al
movzbl%al, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
可以看到,比较大小是在运行期间进行的,这必将影响性能。所以,不推荐这种方式。
movl $-2147483648, -4(%ebp)
的意思是将 -2147483648
压栈,其实就是放到内存中,由于 -2147483648
是负数,在内存中存放的是补码形式(也就是 0x80000000)。
cmpl $2147483647, -4(%ebp)
的意思是比较 2147483647
(0x7FFFFFFF)和 0x80000000 的大小,比较的结果存放到标志寄存器的对应位中。
setne%al
的意思是如果比较的结果为不相等(明显 0x7FFFFFFF 和 0x80000000 并不相等),将 al
寄存器置 1
(0x01)
movzbl
指令负责拷贝一个字节,并用 0
填充其目的操作数中的其余各位。因此,movzbl%al, %eax
指令执行后,寄存器 eax
(也就是返回值)为 0x00000001
。
上一篇: Java毕业设计:Java长沙城市文化展示系统毕业设计源代码作品和开题报告
下一篇: 全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字符串
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。