【问题标题】:Difference between "implicit declaration of function" and the original version of the function“隐式声明函数”与函数原始版本的区别
【发布时间】:2013-04-27 23:32:18
【问题描述】:

我使用 gcc4.8。我写了这样的代码,使用睡眠。

int main(int argc, char *argv[])
{
    /* I know it's worong to pass a floating number to sleep
     * this is only for testing. */
    sleep(0.001);               
    return 0;
}

我用“gcc -Wall a.c -o a”编译它,得到警告“函数‘睡眠’的隐式声明[-Wimplicit-function-declaration]”。然后我运行它,这个程序睡眠大约 1 秒(似乎 sleep ceils 0.001 到 1)。

然后我把代码改成这样:

#include <unistd.h> /* add header file */
int main(int argc, char *argv[])
{
    /* I know it's worong to pass a floating number to sleep
     * this is only for testing. */
    sleep(0.001);               
    return 0;
}

这次它只睡了 0 秒,好像睡眠层从 0.001 到 0。

这两个睡眠不应该是一样的吗?

【问题讨论】:

    标签: c linux sleep


    【解决方案1】:

    在第一种(错误的)情况下,由于假设sleep 的原型采用浮点值(实际上是double),所以将一个真正的浮点值赋予睡眠。 sleep 会将这个double 的位表示解释为int 并等待这么多秒。你很幸运,这只有 1 秒。在第二种情况下,浮点值被强制转换为 int,并舍入为 0。

    【讨论】:

    • 那么第一种情况就像是在开头加上unsigned int sleep(double t);
    • 更有可能是int sleep(double t),因为传统上隐式声明总是返回int。这可以追溯到 C 的早期版本。
    【解决方案2】:

    恕我直言,请始终使用-Werror=implicit-function-declaration 编译选项以防止编译器/链接器的智能默认行为造成损坏。

    通过将这两个案例编译成两个可执行文件sleep_include_no(第一个错误案例,不包含)和sleep_include_yes(第二个案例,包含包含),我做了一些简单的测试:

    ## 'sleep' will invoke the 'nanosleep', and use strace to show real duration
    $ strace ./sleep_include_no 2>&1 | grep nanosleep
    clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffee0165970) = 0
    
    $ strace ./sleep_include_yes 2>&1 | grep nanosleep
    clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=0}, 0x7ffce92eedd0) = 0
    

    gdb 的 disas 命令输出的 asm 代码片段:

    /* 1st wrong case (without include), compiler/linker's default behavior, \
       an immediate constant '0x1' is set into %eax as parameter of sleep */
    => 0x0000555555555141 <+8>:     movsd  0xebf(%rip),%xmm0        # 0x555555556008
       0x0000555555555149 <+16>:    movsd  %xmm0,-0x8(%rbp)
       0x000055555555514e <+21>:    mov    -0x8(%rbp),%rax
       0x0000555555555152 <+25>:    movq   %rax,%xmm0
       0x0000555555555157 <+30>:    mov    $0x1,%eax
       0x000055555555515c <+35>:    call   0x555555555030 <sleep@plt>
    
    /* 2nd ok case (with include), \
       the 'cvttsd2si' instruction is something like cast double to int */
    => 0x0000555555555141 <+8>:     movsd  0xebf(%rip),%xmm0        # 0x555555556008
       0x0000555555555149 <+16>:    movsd  %xmm0,-0x8(%rbp)
       0x000055555555514e <+21>:    movsd  -0x8(%rbp),%xmm0
       0x0000555555555153 <+26>:    cvttsd2si %xmm0,%rax
       0x0000555555555158 <+31>:    mov    %eax,%edi
       0x000055555555515a <+33>:    call   0x555555555030 <sleep@plt>
    

    所以编译器/链接器只是让它工作,但可能不是你所期望的。我认为这是因为编译器有很多遗留功能需要处理和兼容性考虑,我们不应该责怪它。作为程序员,我唯一能做的就是使用-Werror=implicit-function-declaration 发出强制警报。

    附:分享一个由于不包含用户定义的 API 头文件和忽略 implicit declaration of function 警告而引发的血腥生产错误。这是演示代码(3个源文件):

    $ cat my_lib.h
    #ifndef _my_lib_h_
    #define _my_lib_h_
    long long get_i64_from_my_lib(); 
    #endif
    
    $ cat my_lib.c
    #include "my_lib.h"
    long long get_i64_from_my_lib() {
            return 113840990519587; /* 6789 ABCD 0123 */
    }
    
    $ cat main.c 
    #include <stdio.h>
    /* #include "my_lib.h" (without this include, the result is buggy) */
    int main() {
            long long i64 = get_i64_from_my_lib();
            printf("%lld, %0lx\n", i64, i64);
    }
    
    $ gcc -g -c my_lib.c
    $ ar -cq my_lib.a my_lib.o
    $ gcc -g -o my_exe main.c my_lib.a  ## emit implicit-function-declaration warning
    
    ## The returned type is not an expected i64, but a truncated i32. \
    ## When the returned value is less than 2^31, the function seems ok, \
    ## so it is an evil bug.
    $ ./my_exe 
    -1412628189, ffffffffabcd0123
    

    【讨论】:

      猜你喜欢
      • 2021-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-28
      • 2022-01-16
      • 2011-10-05
      • 2013-10-28
      相关资源
      最近更新 更多