要命的编译警告——指针参数类型混乱

前两天在写程序的过程中发现一个问题,编译后运行结果总是不对,修改了很多回算法都不对。由于整个项目代码过长,所以抽出出错的模型重新写一个简单的易于表述的程序,如下:


 1#include                                                                                                   
 2                                                                                                                    
 3void myfunc(unsigned long long *data, unsigned int size)                                                            
 4{                                                                                                                   
 5        *data = size;                                                                                               
 6}                                                                                                                   
 7                                                                                                                    
 8int main()                                                                                                          
 9{                                                                                                                   
10        unsigned char b1 = 0;                                                                                       
11        unsigned short b2 = 0;                                                                                      
12        unsigned int b4 = 0;                                                                                        
13        unsigned long long b8 = 0;                                                                                  
14                                                                                                                    
15        printf("b1=%u, b2=%u, b4=%u, b8=%lu\n", b1, b2, b4, b8);                                                    
16                                                                                                                    
17        myfunc(&b1, 1);                                                                                             
18        myfunc(&b2, 2);                                                                                             
19        myfunc(&b4, 4);                                                                                             
20        myfunc(&b8, 8);                                                                                             
21                                                                                                                    
22        printf("b1=%u, b2=%u, b4=%u, b8=%lu\n", b1, b2, b4, b8);                                                    
23                                                                                                                    
24                                                                                                                    
25        return 0;                                                                                                   
26}


猜测一下这段代码在x86_64体系结构上gcc编译后的运行结果是什么?


太容易了,必然是:

b1=0, b2=0, b4=0, b8=0
b1=1, b2=2, b4=4, b8=8

好吧,错!


这是一个有问题的程序,编译器(太次的编译器不算)会打出类似这样的警告信息:

warning: passing argument 1 of 'myfunc' from incompatible pointer type

但是多数情况下会编译通过,并生成可执行文件,对于习惯性忽略编译警告的人,特别是当编译一个很大的项目出现很多编译警告的时候,这个问题往往就被忽略掉了,而直接使用编译出来的可执行文件。但是执行后发现执行结果却可能是(在不同的体系结构或编译器下可能还会有不同):

b1=0, b2=0, b4=0, b8=0
b1=0, b2=0, b4=4, b8=8


这是为什么?我明明传递了b1的指针,然后把b1指针的内容写成了1,b2也类似如此。为什么这两个会是0呢?

可能经验值高一点的C程序员已经拍脑门猜到问题的可能所在了。由于原始问题代码过于复杂,我首先怀疑的是算法代码写的问题。后来确认算法代码的正确性后我开始使用gdb调试,我发现当myfunc(&b1, 1);执行之后b1的值是对的,是1没错。但是当myfunc(&b2, 2);执行之后b1就变成0了,但是b2是对的,是2。


就示例中的简单代码来看,这个时候已经很容易猜到应该是b2的赋值覆盖了b1。我当时的推测是后续大字节数在栈中和前面小字节数使用同一块四字节空间,为了验证我大想法,我使用了最直接了当大方式——反汇编:

objdump -d mytest

看到反汇编代码后答案一目了然,看一下主要大两个函数:


1230000000000400530:                                                                                         
124  400530:       55                      push   %rbp                                                                
125  400531:       48 89 e5                mov    %rsp,%rbp                                                           
126  400534:       48 89 7d f8             mov    %rdi,-0x8(%rbp)                                                     
127  400538:       89 75 f4                mov    %esi,-0xc(%rbp)                                                     
128  40053b:       8b 55 f4                mov    -0xc(%rbp),%edx                                                     
129  40053e:       48 8b 45 f8             mov    -0x8(%rbp),%rax                                                     
130  400542:       48 89 10                mov    %rdx,(%rax)                                                         
131  400545:       5d                      pop    %rbp                                                                
132  400546:       c3                      retq                                                                       
133                                               
1340000000000400547:                                                                                           
135  400547:       55                      push   %rbp                                                                
136  400548:       48 89 e5                mov    %rsp,%rbp                                                           
137  40054b:       48 83 ec 10             sub    $0x10,%rsp                                                          
138  40054f:       c6 45 ff 00             movb   $0x0,-0x1(%rbp)                                                     
139  400553:       66 c7 45 fc 00 00       movw   $0x0,-0x4(%rbp)                                                     
140  400559:       c7 45 f8 00 00 00 00    movl   $0x0,-0x8(%rbp)                                                     
141  400560:       48 c7 45 f0 00 00 00    movq   $0x0,-0x10(%rbp)                                                    
142  400567:       00                                                                                                 
143  400568:       48 8b 75 f0             mov    -0x10(%rbp),%rsi                                                    
144  40056c:       8b 4d f8                mov    -0x8(%rbp),%ecx                                                     
145  40056f:       0f b7 45 fc             movzwl -0x4(%rbp),%eax                                                     
146  400573:       0f b7 d0                movzwl %ax,%edx                                                            
147  400576:       0f b6 45 ff             movzbl -0x1(%rbp),%eax                                                     
148  40057a:       0f b6 c0                movzbl %al,%eax                                                            
149  40057d:       49 89 f0                mov    %rsi,%r8                                                            
150  400580:       89 c6                   mov    %eax,%esi                                                           
151  400582:       bf a0 06 40 00          mov    $0x4006a0,%edi                                                      
152  400587:       b8 00 00 00 00          mov    $0x0,%eax                                                           
153  40058c:       e8 7f fe ff ff          callq  400410                                                  
154  400591:       48 8d 45 ff             lea    -0x1(%rbp),%rax                                                     
155  400595:       be 01 00 00 00          mov    $0x1,%esi                                                           
156  40059a:       48 89 c7                mov    %rax,%rdi                                                           
157  40059d:       e8 8e ff ff ff          callq  400530158  4005a2:       48 8d 45 fc             lea    -0x4(%rbp),%rax                                                     
159  4005a6:       be 02 00 00 00          mov    $0x2,%esi                                                           
160  4005ab:       48 89 c7                mov    %rax,%rdi                                                           
161  4005ae:       e8 7d ff ff ff          callq  400530162  4005b3:       48 8d 45 f8             lea    -0x8(%rbp),%rax                                                     
163  4005b7:       be 04 00 00 00          mov    $0x4,%esi                                                           
164  4005bc:       48 89 c7                mov    %rax,%rdi                                                           
165  4005bf:       e8 6c ff ff ff          callq  400530166  4005c4:       48 8d 45 f0             lea    -0x10(%rbp),%rax                                                    
167  4005c8:       be 08 00 00 00          mov    $0x8,%esi                                                           
168  4005cd:       48 89 c7                mov    %rax,%rdi                                                           
169  4005d0:       e8 5b ff ff ff          callq  400530170  4005d5:       48 8b 75 f0             mov    -0x10(%rbp),%rsi                                                    
171  4005d9:       8b 4d f8                mov    -0x8(%rbp),%ecx                                                     
172  4005dc:       0f b7 45 fc             movzwl -0x4(%rbp),%eax                                                     
173  4005e0:       0f b7 d0                movzwl %ax,%edx                                                            
174  4005e3:       0f b6 45 ff             movzbl -0x1(%rbp),%eax                                                     
175  4005e7:       0f b6 c0                movzbl %al,%eax                                                            
176  4005ea:       49 89 f0                mov    %rsi,%r8                                                            
177  4005ed:       89 c6                   mov    %eax,%esi                                                           
178  4005ef:       bf a0 06 40 00          mov    $0x4006a0,%edi                                                      
179  4005f4:       b8 00 00 00 00          mov    $0x0,%eax                                                           
180  4005f9:       e8 12 fe ff ff          callq  400410                                                  
181  4005fe:       b8 00 00 00 00          mov    $0x0,%eax                                                           
182  400603:       c9                      leaveq                                                                     
183  400604:       c3                      retq                                                                       
184  400605:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)                                                
185  40060c:       00 00 00                                                                                           
186  40060f:       90                      nop            


第134行main函数开始,135~137行分配了main存储临时变量的栈空间。138~141行定义并初始化临时变量b1, b2, b4, b8:


138   movb   $0x0,-0x1(%rbp)    <--- b1
139   movw   $0x0,-0x4(%rbp)   <--- b2
140   movl   $0x0,-0x8(%rbp)     <--- b4
141   movq   $0x0,-0x10(%rbp)  <--- b8

很一目了然,内存中的组织形式大概如下:


31       16       0  bit                                                                                                                                              
+--------+--------+                                                                                                                                                   
|   b2   |      b1|  0x04                                                                                                                                             
+--------+--------+                                                                                                                                                   
|       b4        |  0x08                                                                                                                                             
+--------+--------+                                                                                                                                                   
|        b        |  0x0c                                                                                                                                             
|        8        |  0x10                                                                                                                                             
+-----------------+


我们看,b1和b2在同一个四字节空间里,b1占在了第一个自己,b2占在了第三和第四字节。b4占了b2下面四个字节,b8占了b4下面八个字节。


下面看第154~157行,也就是调用myfunc(&b1, 1)的时候。

  lea    -0x1(%rbp),%rax  把b1的地址存在rax寄存器里。

  mov    $0x1,%esi 把参数size,也就是数值1存在esi寄存器里。

  mov    %rax,%rdi 又把上面rax存放的内容,也就是b1的地址,存到rdi寄存器里。

  callq  400530调用myfunc函数。


再看myfunc函数,由于我没有使用优化编译的选项,所以这个编译出来的代码过于冗余。myfunc函数那么多行,先是存放参数的寄存器内容入栈,然后又把参数出栈存到另外的寄存器里,然后最后赋值。其实最主要的就是这行:

mov    %rdx,(%rax)

把1写入b1所在的地址空间。注意这是一个8字节的操作,实际是写入从&b1所在地址开始的8个字节。但是我想是因为字节对齐机制的保护作用,所以&b1之上的内容,也就是其它整数段的内容没有被覆盖。


但是当第二次调用myfunc(&b2, 2)的时候,b1的空间无法幸免于难,被b2无情的刷掉了。

第三次调用myfunc(&b4, 4)的时候,b1和b2都无法幸免,因为是当作8字节操作的,所以b1,b2连上b4一起被b4刷掉了。

第四次调用myfunc(&b8, 8)的时候,由于b8拥有8字节的空间,所以它的赋值没有干扰到别人。


我想现在问题就清楚了,这样一个运行时才可能出现的问题,如果你忽略编译警告就危险了。而且问题远远比我简化后举例说明的要复杂。试想一下:

一个几万几十万甚至千万行代码的项目,你调用一个不知道谁写的函数,这个函数就类似myfunc(),然后你传入参数的方式又和上面类似。经过一个多小时的编译,打印了数百行行不同的警告信息,但编译通过。你简单测试之后由于测试没有覆盖到有问题的分支和条件导致这个问题被雪藏了。

之后你发布出去新版本的软件,被用户大量使用后发现软件运行不稳定,在不知道什么情况下就会出现莫名其妙的错误。然后用户大量反馈问题给你,说你负责的功能在使用时有很难预知的问题,但是不知道如何稳定的复现,只有不断的运行没准多少天能碰见一次错误。但是这个错误又不是致命错误,不会引起coredump,你又无法获得到方面定位问题的core文件。

这个时候你一定会感觉世界末日到了,根本无从下手调试。数万行的代码,你写了其中几千行,用户说你这几千行代码负责的功能用着有问题。你无从下手调试,没有人知道哪有问题,也非常难复现。你开始反复的苦苦的阅读着你那几千行可能自己都快记不清意思的代码,费尽的读了很多很多遍都找不到算法逻辑的错误(因为算法上确实没有错误。。。)

怎么办?崩溃了吧?仔细看一下编译警告,问题就容易隐藏在那里。这种不好重现,不能定位,不是你一个人的代码,又与你代码的逻辑算法无关的bug会把一个程序员弄的完全崩溃的。问题绝对不会像本文示例的那样简明清楚,问题往往是比捞MH370还难定位。所以,养成良好的编码习惯,注意编译警告,增加对系统机制的理解都有助于避免和定位错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值