atoll的陷阱

atoll谁都知道是字符串转long long。
但是日前发现字符串里的数字超过11位竟然就溢出了,好奇怪啊。

#include <stdio.h>
int main(int argc,char**argv) {
        char bigNumStr[] = "12345678901";
        long long llNum;
        llNum = atoll(bigNumStr);
        printf("%lld\n",llNum);
        return 0;
}

看到这应该就有老鸟就笑了。没有#include 《stdlib.h》嘛。
但是为什么呢?
抠嗤了好一会,发现在没有stdlib.h的时候
编译器产生的汇编代码调用atoll的时候是这样的:

call    atoll
cltd
movl    %eax, 40(%esp)
movl    %edx, 44(%esp)

而在有stdlib.h的时候
编译器产生的汇编代码调用atoll的时候是这样的:

call    atoll
movl    %eax, 40(%esp)
movl    %edx, 44(%esp)

这个cltd是神马东西呢?
这个cltd其实就是把32位数转换为64位数,这里就是一个比较常见的隐式转换。
但是返回值为long long的atoll为毛会返回一个32位数呢?
其实你在代码里随便调用一个不存在的函数,用-Wall选项编译就会发现如下提示:

atoll.c:6:2: warning: implicit declaration of function 'notExist' [-Wimplicit-function-declaration]
  printf("%lld %s\n",llNum,notExist(llNum));
  ^
atoll.c:6:2: warning: format '%s' expects argument of type 'char *', but argument 3 has type 'int' [-Wformat=]

gcc把未定义的notExist的返回值自作主张的认定为int了。
so,答案就这样揭晓了。
应为调用atoll的时候没有包含它的声明文件stdlib.h。
所以在编我们的代码的时候,比如atoll.c,也就同样自认为返回值是int。
但是在链接程序的时候libc.so里是有atoll的实现的。如下:

[cz@localhost ~]$ nm /usr/lib/libc.so.6 | grep atoll
0002f8b0 T atoll

所以链接并没有出错。所以就造成了这个奇怪的问题。我们的代码里把atoll的返回值当成了int,赋值给long long类型,同时做隐式类型转换。cltd用eax的数值扩展,高位存到edx,所以之前的edx就被覆盖调了。

发表评论?

0 条评论。

发表评论


请输入正确的验证码