移位溢出

本文简要介绍左移导致的溢出问题。

实际项目中需要计算SD卡中某个目录的大小,并判断该目录所占空间是否超过SD卡总容量的一半。 测试过程中经常发现误报,该目录所占空间远小于SD卡容量一半的时候,就上报占用空间过半的事件。 排查发现原来是计算的时候移位导致了溢出。问题代码如下:

unsigned int total_space_in_mb;
unsigned long long used_space_in_byte;

/* total_space_in_mb 左移20位从MB转换为byte, 左移19位相当于总容量的一半 */
used_space_in_byte > (unsigned long long)(total_space_in_mb << (20 - 1));

我们以下面的例子来分析下原因:

#include<stdio.h>
int main()
{
    unsigned int total_space_in_mb = 30208;
    unsigned long long result;

    result = (unsigned long long)(total_space_in_mb << 19);
    printf("wrong result:%llu\n", result); /* wrong result:2952790016 */
    
    result = ((unsigned long long)total_space_in_mb << 19);
    printf("right result:%llu\n", result); /* right result:15837691904 */
	return 0;
}

将十进制表示转换为对应的二进制:

‭30208对应的二进制:                          0111011000000000
‭2952790016对应的二进制:     10110000000000000000000000000000(30208左移19位,溢出2位)
‭15837691904对应的二进制:001110110000000000000000000000000000(30208左移19位,无溢出)‬‬‬

错误的代码中虽然进行了强制类型转换,但是转换发生在移位后,所以无法避免溢出。 正确的代码先进行了类型提升,然后再移位,可以避免溢出。 这点从汇编代码中可以看出。 执行gcc -S test.c可以生产名为test.s的汇编代码文件,内容如下:

	.file	"test.c"
	.section	.rodata
.LC0:
	.string	"wrong result:%llu\n"
.LC1:
	.string	"right result:%llu\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$30208, -12(%rbp)
	movl	-12(%rbp), %eax
	sall	$19, %eax
	movl	%eax, %eax
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, %rsi
	movl	$.LC0, %edi
	movl	$0, %eax
	call	printf
	movl	-12(%rbp), %eax
	salq	$19, %rax
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, %rsi
	movl	$.LC1, %edi
	movl	$0, %eax
	call	printf
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
	.section	.note.GNU-stack,"",@progbits

可以看出, 错误代码调用了32位算数左移指令sall, 正确代码调用了64位算术左移指令salq。 其中eax是寄存器rax的低32位。

参考文档: https://software.intel.com/en-us/articles/introduction-to-x64-assembly https://my.oschina.net/guonaihong/blog/511576

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

Node.js中的内存泄漏分析

内存泄漏(Memory Leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。如果内存泄漏的位置比较关键,那么随着处理的进行可能持有越来越多的无用...

4435
来自专栏码生

Linux 权限代码解析

首先说明: 数字后面为9位由字母或-组成的 每三位对应前面的一个数字 例如:755 rwx 对应第一个7 r-x 对应第二个5 r-x 对应第三个5

1574
来自专栏北京马哥教育

linux实用技巧:你该使用ctags查找源码了

linux实用技巧:你该使用ctags查找源码了 ---- 1.ctags简介: “哦,这个多的文件,我该如何去查看XX函数的实现!”相信...

2996
来自专栏python3

python爬虫常用模块

涉及到网络这块,必不可少的模式就是urllib.request了,顾名思义这个模块主要负责打开URL和HTTP协议之类的

881
来自专栏磨磨谈

rbd的image对象数与能写入文件数的关系

对于这个问题,我原来的理解也是:对象默认设置的大小是4M一个,存储下去的数据,如果小于4M,就会占用一个小于4M的对象,如果超过4M,那么存储的数据就会进行拆分...

1112
来自专栏python3

python3--队列Queue,管道Pipe,进程之间的数据共享,进程池Pool,回调函数callback

既打印了主进程put的值,也打印了子进程put的值,在进程中使用队列可以完成双向通信

3531
来自专栏决胜机器学习

《Redis设计与实现》读书笔记(二十八) ——Redis集群节点结构与槽分配

《Redis设计与实现》读书笔记(二十八) ——Redis集群节点结构与槽分配 (原创内容,转载请注明来源,谢谢) 一、概述 redis集群是...

4396
来自专栏JetpropelledSnake

SNMP学习笔记之Python的netsnmp和pysnmp的性能对比

用python获取snmp信息有多个现成的库可以使用,其中比较常用的是netsnmp和pysnmp两个库。网上有较多的关于两个库的例子。

2432
来自专栏三木的博客

Linux shell 程序设计2——bash的内置命令

常用的内置命令忽略,来看看shell编程中其他一些重要的内置命令: 1、help:显示所有内置命令列表,或显示一个具体命令的用法。 -s: 表示列出命令的语法...

2116
来自专栏技巅

Glusterfs之rpc模块源码分析(中)之Glusterfs的rpc模块实现(1)

1743

扫码关注云+社区

领取腾讯云代金券