例题描述
例题描述:通过C语言识别一个int型数据在十进制下是否为回文数字。不能有额外的字符串空间开销。
如:2156512是回文数,而21565不是回文数。
问题分析:
1. 当这个数字是负数的时候,肯定不是回文数
2. 可以将这个数翻转,判断翻转后是否相同
C语言代码演示:
/*************************************************************************
> File Name: isPalindrome.cpp
> Author: ChenXiansen
> Mail: 1494089474@qq.com
> Created Time: Wed 11 Nov 2020 09:49:43 AM CST
************************************************************************/
#include <stdio.h>
bool isPalindrome(int x, int n) {
if (__builtin_expect(!!(x < 0), 0)) return false;
int y = 0, z = x;
while (x) {
y = y * n + x % n;
x /= n;
}
return z == y;
}
int main() {
int n, Cov = 10;
scanf("%d", &n);
if (isPalindrome(n, Cov)) {
printf("Num: %d in Cov: %d is a reverse num!\n", n, Cov);
} else {
printf("NO! It's not a reverse num!\n");
}
return 0;
}
代码分析
在 isPalindrome函数中,使用了if (__builtin_expect(!!(x < 0), 0)) return false; 这条语句。
下面是对__builtin_expect宏的表述:
GCC提供了__builtin_expect宏,作为编译分支时候的暗示。用法是__builtin_expect(var, expected_value),也就是说,告诉编译器var这个变量的值比较可能是什么。在kernel中这个宏被用在likely和unlikely这两个宏定义中:
#define likely(x) __builtin_expect(!!(x), 1) //x很可能成立 #define unlikely(x) __builtin_expect(!!(x), 0) //x很可能不成立
从现代的处理器架构说起。相信大家都知道流水线技术,就是CPU可以在统一个时钟周期内同时执行多条指令,当前指令尚未执行完毕,实际上就已经开始处理后面的指令了。然而当处理器遇到分支的时候,就无法判断即将执行的是哪个分支,流水线优化就受到了限制。
后来,随着处理器技术的发展,处理器开始直接预取分支后面的指令,如果发现分支预判错误,则抛弃之前的执行结果,重新转入正确的分支继续执行。 更加现代的处理器甚至能够预取更多后面的指令,对于不依赖之前执行结果的指令都可以按照一定的规则预先执行得到结果。
汇编代码
1. 初始代码汇编
接下来在终端运行gcc -S isPalindrome.cpp,查看汇编代码中的isPalindrome()函数:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl -20(%rbp), %eax
shrl $31, %eax
movzbl %al, %eax
testq %rax, %rax
je .L2
movl $0, %eax
jmp .L3
.L2:
movl $0, -8(%rbp)
movl -20(%rbp), %eax
movl %eax, -4(%rbp)
.L5:
cmpl $0, -20(%rbp)
je .L4
movl -8(%rbp), %eax
imull -24(%rbp), %eax
movl %eax, %ecx
movl -20(%rbp), %eax
cltd
idivl -24(%rbp)
movl %edx, %eax
addl %ecx, %eax
movl %eax, -8(%rbp)
movl -20(%rbp), %eax
cltd
idivl -24(%rbp)
movl %eax, -20(%rbp)
jmp .L5
.L4:
movl -4(%rbp), %eax
cmpl -8(%rbp), %eax
sete %al
.L3:
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z12isPalindromeii, .-_Z12isPalindromeii
.section .rodata
对.LFB0段的释义:
- main()函数中的变量n的值存入了
edi寄存器,作为bool isPalindrome(int x, int n)的形参x。 - main()函数中的变量Cov的值存入
esi寄存器,作为bool isPalindrome(int x, int n)的形参n。 - movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
将这两个参数压入函数栈。 movl -20(%rbp), %eax:将存在于-20(%rbp)中x的值,存入eax寄存器。- 执行以下汇编指令,这段汇编指令由
if (__builtin_expect(!!(x < 0), 0)) return false;转换得到:
可以看到:在执行前三行语句后(这三行代码现在没有弄清楚具体含义,待后续查阅相关资料),下一步是判断分支:汇编代码是shrl $31, %eax movzbl %al, %eax testq %rax, %rax je .L2 movl $0, %eax jmp .L3je .L2,- 如果满足
testq %rax, %rax条件,下一步是执行C程序中的:
对应在汇编.L2函数中的:int y = 0, z = x;.L2: movl $0, -8(%rbp) movl -20(%rbp), %eax movl %eax, -4(%rbp) - 如果不满足条件,将返回值0放入寄存器eax中,执行.L3 汇编函数部分的return操作。
- 如果满足
修改程序后汇编:
现在将
if (__builtin_expect(!!(x < 0), 0)) return false;
这部分C代码转换成
if (x < 0) return false;
观察执行预处理-编译-汇编过程后,查看汇编代码中的isPalindrome()函数。(由于.L2之后的部分完全相同,因此没有在博客中显示)
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
cmpl $0, -20(%rbp)
jns .L2
movl $0, %eax
jmp .L3
可以看到:将函数参数全部压栈后,进行的分支判断:
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
cmpl $0, -20(%rbp)
jns .L2
movl $0, %eax
jmp .L3
cmpl $0, -20(%rbp):如果 bool isPalindrome(int x, int n)函数中 参数x的值不满足 x < 0,则执行.L2之后的汇编函数,反之直接return 0.
参考资料:
https://zhuanlan.zhihu.com/p/27339191
http://deltamaster.is-programmer.com/posts/37285.html
附录:完整汇编程序代码:
.file "isPalindrome.cpp"
.text
.globl _Z12isPalindromeii
.type _Z12isPalindromeii, @function
_Z12isPalindromeii:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl -20(%rbp), %eax
shrl $31, %eax
movzbl %al, %eax
testq %rax, %rax
je .L2
movl $0, %eax
jmp .L3
.L2:
movl $0, -8(%rbp)
movl -20(%rbp), %eax
movl %eax, -4(%rbp)
.L5:
cmpl $0, -20(%rbp)
je .L4
movl -8(%rbp), %eax
imull -24(%rbp), %eax
movl %eax, %ecx
movl -20(%rbp), %eax
cltd
idivl -24(%rbp)
movl %edx, %eax
addl %ecx, %eax
movl %eax, -8(%rbp)
movl -20(%rbp), %eax
cltd
idivl -24(%rbp)
movl %eax, -20(%rbp)
jmp .L5
.L4:
movl -4(%rbp), %eax
cmpl -8(%rbp), %eax
sete %al
.L3:
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z12isPalindromeii, .-_Z12isPalindromeii
.section .rodata
.LC0:
.string "%d"
.align 8
.LC1:
.string "Num: %d in Cov: %d is a reverse num!\n"
.LC2:
.string "NO! It's not a reverse num!"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movl $10, -12(%rbp)
leaq -16(%rbp), %rax
movq %rax, %rsi
leaq .LC0(%rip), %rdi
movl $0, %eax
call __isoc99_scanf@PLT
movl -16(%rbp), %eax
movl -12(%rbp), %edx
movl %edx, %esi
movl %eax, %edi
call _Z12isPalindromeii
testb %al, %al
je .L7
movl -16(%rbp), %eax
movl -12(%rbp), %edx
movl %eax, %esi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
jmp .L8
.L7:
leaq .LC2(%rip), %rdi
call puts@PLT
.L8:
movl $0, %eax
movq -8(%rbp), %rcx
xorq %fs:40, %rcx
je .L10
call __stack_chk_fail@PLT
.L10:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
本文介绍了一种使用C语言判断整数是否为回文数的方法,并深入探讨了__builtin_expect宏在编译优化中的作用。通过对代码的逐行分析及对应的汇编指令解释,展示了如何高效地实现该功能。

2188

被折叠的 条评论
为什么被折叠?



