“32bit 机器上gettimeofday() 返回值的tv_sec 要先强制转成long long类型”的问题

来源:互联网 发布:意大利设计风格知乎 编辑:程序博客网 时间:2024/06/17 00:01

今天遇到一个和gettimeofday()相关奇怪的问题,问题描述如下。

有如下代码,在32bit CentOS 6.3平台上编译后运行

#include <iostream>#include <sys/time.h>using namespace std;int main(void) {        std::cout << __WORDSIZE << std::endl;        struct timeval tm;        gettimeofday(&tm, NULL);        cout << "tm.tv_sec: " << tm.tv_sec << endl;        cout << "tm.tv_usec: " << tm.tv_usec << endl;        long long timestamp = tm.tv_sec * 1000;        cout << "timestamp: " << timestamp << endl;        return 0;}
运行结果如下。

32tm.tv_sec: 1384764898tm.tv_usec: 303888timestamp: 1785428688
这里的32表明是32bit机器。tm.tv_sec 和 tm.tv_usec的值都是正确的,但timestamp值异常,应该是1384764898000,结果像是被截断了,成了1785428688。


修改第13行代码如下。即,先将tm.tv_sec强制类型转换成long long 类型,然后再做计算。

#include <iostream>#include <sys/time.h>using namespace std;int main(void) {        std::cout << __WORDSIZE << std::endl;        struct timeval tm;        gettimeofday(&tm, NULL);        cout << "tm.tv_sec: " << tm.tv_sec << endl;        cout << "tm.tv_usec: " << tm.tv_usec << endl;        long long timestamp = (long long)tm.tv_sec * 1000;        cout << "timestamp: " << timestamp << endl;        return 0;}
这样改过后,结果正确。
32tm.tv_sec: 1384764980tm.tv_usec: 331110timestamp: 1384764980000

但是为什么会这样?timestamp是long long类型的,8字节,足够承载最终计算出来的值,所以像是问题出在tm.tv_sec上,但是tm.tv_sec本身输出的值也是正确的~~~


我猜原因大概是这样的:

1384764980 * 1000的值已经超出32位有符号int所能表示的最大上限(2 ^ 31 - 1 = 2147483647)了。

同时,long long timestamp = tm.tv_sec * 1000很可能被编译器先转成了下面的样子(32位系统下,tm.tv_sec的数据类型 time_t 是4字节int)

int tmp = tm.tv_sec * 1000;

long long timestamp = tmp;

在第一步就已经发生溢出了,第二步赋值给long long型变量还是溢出的值!


下面用一个小例子 t.cpp 来验证上面的猜测。

int main(void) {int intValue = 2100000000;long long llValue = intValue * 10;return 0;}


通过 g++ -save-temps -S t.cpp 命令得到g++编译 t.cpp 过程中产生的汇编文件 t.s

基本上就是,用32位的寄存器做乘10的运算,结果也存在32位eax寄存器中(16-21行,这时已经溢出了),然后在赋值给llValue(22-25行)的时候,扩展成64位寄存器(edx, eax)表示



=======================================

对 t.cpp 稍作修改如下,即在 intValue 做乘10操作前,强制类型转换成 long long 类型

int main(void) {int intValue = 2100000000;long long llValue = (long long)intValue * 10;return 0;}

然后,得到新的汇编代码如下

.file"x.cpp".text.globl main.typemain, @functionmain:.LFB0:.cfi_startproc.cfi_personality 0x0,__gxx_personality_v0pushl%ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl%esp, %ebp.cfi_def_cfa_register 5andl$-8, %esppushl%ebxsubl$20, %espmovl$2100000000, 4(%esp)movl4(%esp), %eaxmovl%eax, %edxsarl$31, %edximull$10, %edx, %ecximull$0, %eax, %ebx.cfi_escape 0x10,0x3,0x7,0x75,0x0,0x9,0xf8,0x1a,0x34,0x1caddl%ebx, %ecxmovl$10, %ebxmull%ebxaddl%edx, %ecxmovl%ecx, %edxmovl%eax, 8(%esp)movl%edx, 12(%esp)movl%eax, 8(%esp)movl%edx, 12(%esp)movl$0, %eaxaddl$20, %esppopl%ebx.cfi_restore 3movl%ebp, %esp.cfi_def_cfa_register 4popl%ebp.cfi_restore 5.cfi_def_cfa_offset 4ret.cfi_endproc.LFE0:.sizemain, .-main.ident"GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-3)".section.note.GNU-stack,"",@progbits


大致的意思就是,通过下面两条指令将数字2100000000保存在eax寄存器中:

movl $2100000000, 4(%esp)

movl 4(%esp), %eax

然后通过无符号乘法运算mull对eax做乘10操作,结果的高32位保存在edx中,低32位保存在eax中

movl $10, %ebx

mull %ebx ;eax * ebx = edx,eax

2100000000的符号位通过算术右移31位获取到,然后也乘10,再加到结果的高32位edx寄存器中。

即,用两个32位寄存器来表示最终的计算结果,于是没有发生溢出!


前面的猜测基本正确~~~

原创粉丝点击