美文网首页
当C语言隐式函数声明遇到printf

当C语言隐式函数声明遇到printf

作者: creepycool | 来源:发表于2017-08-10 16:43 被阅读0次

    最近在编程时遇到一个bug,通过gdb定位到printf函数,却怎么也找不到原因。从网上这篇博客中发现了问题所在:http://blog.csdn.net/smstong/article/details/50523120

    还有一篇,是作者给出的实例,和我遇到的问题一模一样:http://blog.csdn.net/smstong/article/details/50767380

    0x00 遇到的问题

    在C语言网络编程时,功能是打印IP地址和端口号,如下,乍一看并没有什么错误。

    printf("SRC: %s:%d\n", inet_ntoa(ipheader->ip_src), ntohs(tcpheader->th_sport));
    printf("DST: %s:%d\n", inet_ntoa(ipheader->ip_dst, ntohs(tcpheader->th_dport));
    

    可是,程序一运行,就报segment fault,通过gdb调试发现ipheader也并没有是NULL或者不正确的值,这是为什么呢?

    0x01 原因分析

    通过上面那篇博客链接,发现是inet_ntoa()这个函数调用出了问题。那篇博客中讲到,C语言编译器对于没有声明原型的函数,通通作为返回类型为整数的函数来处理,参数的类型则由调用的实参自动提升后确定。例如:

    non_exit_function(12, 'c');
    

    在编译时,这个没有事先声明的函数将被当作如下形式:

    int non_exist_function(int, int);
    

    这里,'c'被提升为了int类型。

    所以,在我的代码中,

    inet_ntoa(ipheader->ip_src);
    

    被当作这样的函数原型处理了:

    int inet_ntoa(addr_in);
    

    然而,实际上这个函数原型应该是:

    extern char* inet_ntoa(struct in_addr __in);
    

    这样一来,返回值本来是指针类型,却被截断成了int类型。对于32位系统来说,由于指针类型和int类型长度相同,都是32位,所以恰好不会出现截断的情况,编译运行也是正常的。

    然而在64位系统下,char *是64位,int仍然是32位,所以就出现了截断问题。

    由于printf(char *, ...)是个变参函数,所以调用它时,编译器不会检查可变参数的数据类型,而是按照实参类型进行准备参数入栈。相当于:

    printf("from %s:%d", 123, 80);
    

    指示符%s对应的第一个参数类型却是int,从而导致printf()函数内部在通过va_arg()提取参数时产生错误,最终导致段错误。

    这种段错误非常非常隐蔽,编译时又不会报错。所以出现之后并不能很容易的找出来。如果我们把代码改成这样:

    char* sip = inet_ntoa(ipheader->ip_src);
    printf("SRC: %s:%d\n", inet_ntoa(sip), ntohs(tcpheader->th_sport));
    

    编译器就会给出警告信息:

    warning: initialization makes pointer from integer without a cast [enabled by default]
    

    所以说,虽然printf函数内加上函数调用,代码看起来只有一行,却埋下了编译器不会发生警告的种子。所以,将这样的语句分开写,该函数调用就函数调用,该传参就传参,是种良好的编程习惯。

    0x02 解决方法

    GCC有个开关选项:-Wimplicit-function-declaration。只要把这个选项加上,编译器就会对所有的隐式声明函数的调用发出警告,非常方便。

    $ gcc -Wimplicit-function-declaration -o tcpsyndos tcpsyndos.c -lpcap
    tcpsyndos.c: In function ‘main’:
    tcpsyndos.c:96:9: warning: implicit declaration of function ‘inet_ntoa’ [-Wimplicit-function-declaration]
             printf("DST IP: %s\n", inet_ntoa(iphdr->ip_dst.s_addr));
             ^
    tcpsyndos.c: In function ‘TCP_RST_send’:
    tcpsyndos.c:216:5: warning: implicit declaration of function ‘close’ [-Wimplicit-function-declaration]
         close(rawsocket);
         ^
    

    如何解决这种警告呢?将含有函数原型声明的头文件包含进来就可以了。从编译器发出的警告看来,我缺少了两种头文件(当然,本人忘记了加这两个头文件非常愚蠢0_0)。

    #include <arpa/inet.h>
    #include <unistd.h>
    

    相关文章

      网友评论

          本文标题:当C语言隐式函数声明遇到printf

          本文链接:https://www.haomeiwen.com/subject/zqghrxtx.html