【问题标题】:Why am I able to link without including ctype.h为什么我可以在不包含 ctype.h 的情况下进行链接
【发布时间】:2015-11-17 12:29:14
【问题描述】:
  • 没有#include<ctype.h>,下面的程序输出1和0。
  • 使用包含,它输出 1 和 1。

我正在使用 TDM-GCC 4.9.2 64 位。我想知道isdigit在第一种情况下的实现是什么,以及为什么能够链接

#include<stdio.h>
//#include<ctype.h>
int main()
{
    printf("%d %d\n",isdigit(48),isdigit(48.4));
    return 0;
}

【问题讨论】:

  • 会不会是ctype.h把isdigit重新定义为宏?
  • @WouterHuysentruit 不,我没有隐瞒任何事情。此外,如果您只是将程序复制并粘贴到 ideone 中,它似乎可以在 GCC 5.1 上编译和链接,但它会崩溃。

标签: c gcc gcc4.9


【解决方案1】:

默认情况下,GCC 使用允许隐式声明的 C90 标准(带有 GNU 扩展 (reference))。您的情况的问题是,您有两个使用两个不同参数对 isdigit 的调用,这可能会使编译器在创建函数的隐式声明时感到困惑,并且它可能会选择 int isdigit(double) 以确保安全。这当然是错误的函数原型,这意味着当库函数在运行时被调用时,它会被错误的参数调用,并且您将有未定义的行为

当你包含&lt;ctype.h&gt; 头文件时,有一个正确的原型,然后编译器知道isdigit 接受一个int 参数并且可以将double 文字48.4 转换为整数@987654330 @ 来电。


至于为什么要链接,这是因为虽然这些功能可能被实现为宏,但这不是必需的。 的要求是这些功能,至少在 C11 标准中(我目前没有任何旧版本可用),必须了解当前的语言环境,这将使其实现作为宏更难,更容易作为普通库函数。并且由于标准库始终是链接的(除非您另外告诉 GCC),因此这些函数将可用。

【讨论】:

    【解决方案2】:

    首先#include 语句与linking 没有任何关系。请记住,C 中前面带有 # 的任何内容都适用于预处理器,而不是编译器或链接器。

    但那是说函数必须被链接,不是吗?

    让我们分步执行这些步骤。

    $ gcc -c -Werror --std=c99 st.c 
    st.c: In function ‘main’:
    st.c:5:22: error: implicit declaration of function ‘isdigit’ [-Werror=implicit-function-declaration]
         printf("%d %d\n",isdigit(48),isdigit(48.4));
                          ^
    cc1: all warnings being treated as errors
    

    正如您所见,gcc 的 lint(静态分析器)正在运行!

    无论我们将继续忽略它...

    $ gcc -c  --std=c99 st.c 
    st.c: In function ‘main’:
    st.c:5:22: warning: implicit declaration of function ‘isdigit’ [-Wimplicit-function-declaration]
         printf("%d %d\n",isdigit(48),isdigit(48.4));
    

    这一次只是一个警告。现在我们在当前目录中有一个目标文件。让我们检查一下...

    $ nm st.o 
                     U isdigit
    0000000000000000 T main
                     U printf
    

    如您所见,printfisdigit 都被列为未定义。所以代码必须来自某个地方不是吗?

    让我们继续链接它...

    $ gcc st.o
    $ nm a.out | grep  'printf\|isdigit'
                     U isdigit@@GLIBC_2.2.5
                     U printf@@GLIBC_2.2.5
    

    正如您所见,情况略有改善。因为isdigitprintf 不像st.o 那样无助的孤独者。您可以看到这两个功能都是由GLIBC_2.2.5 提供的。但是GLIBC在哪里?

    好吧,让我们进一步检查一下最终的可执行文件...

    $ ldd a.out 
            linux-vdso.so.1 =>  (0x00007ffe58d70000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb66f299000)
            /lib64/ld-linux-x86-64.so.2 (0x000055b26631d000)
    

    啊哈...有那个 libc 。事实证明,虽然您没有给出任何指令,但链接器默认链接了 3 个库,其中一个是 libc,其中包含 printfisdigit

    您可以通过以下方式查看链接器的默认行为:

    $gcc -dumpspec
    *link:
    %{!r:--build-id} %{!static:--eh-frame-hdr} %{!mandroid|tno-android-ld:%{m16|m32|mx32:;:-m elf_x86_64}                    %{m16|m32:-m elf_i386}                    %{mx32:-m elf32_x86_64}   --hash-style=gnu   --as-needed   %{shared:-shared}   %{!shared:     %{!static:       %{rdynamic:-export-dynamic}       %{m16|m32:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:/lib/ld-linux.so.2}}}       %{m16|m32|mx32:;:-dynamic-linker %{muclibc:/lib/ld64-uClibc.so.0;:%{mbionic:/system/bin/linker64;:/lib64/ld-linux-x86-64.so.2}}}       %{mx32:-dynamic-linker %{muclibc:/lib/ldx32-uClibc.so.0;:%{mbionic:/system/bin/linkerx32;:/libx32/ld-linux-x32.so.2}}}}     %{static:-static}};:%{m16|m32|mx32:;:-m elf_x86_64}                    %{m16|m32:-m elf_i386}                    %{mx32:-m elf32_x86_64}   --hash-style=gnu   --as-needed   %{shared:-shared}   %{!shared:     %{!static:       %{rdynamic:-export-dynamic}       %{m16|m32:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:/lib/ld-linux.so.2}}}       %{m16|m32|mx32:;:-dynamic-linker %{muclibc:/lib/ld64-uClibc.so.0;:%{mbionic:/system/bin/linker64;:/lib64/ld-linux-x86-64.so.2}}}       %{mx32:-dynamic-linker %{muclibc:/lib/ldx32-uClibc.so.0;:%{mbionic:/system/bin/linkerx32;:/libx32/ld-linux-x32.so.2}}}}     %{static:-static}} %{shared: -Bsymbolic}}
    

    另外两个库是什么?

    请记住,当您深入了解 a.out 时,printfisdigit 仍然显示为 U,这意味着未知。换句话说,没有与这些符号相关联的memory 地址。

    实际上,这就是魔法所在。这些库实际上是在运行时加载的,而不是像旧系统那样在链接时加载。

    它是如何实现的?好吧,它有一个相关的术语,比如惰性链接。它的作用是,当进程调用一个函数时,如果没有内存地址(TEXT 部分),它会生成一个Trap(类似于高级语言术语中的异常,当控制权移交给语言引擎时) .内核拦截这样的Trap 并将其交给动态加载器,动态加载器加载库并将相关的内存地址返回给调用者进程。

    有多种理论原因,为什么懒惰做事比事先做好。我想这是一个全新的话题,我们将在其他时间讨论。

    【讨论】:

      猜你喜欢
      • 2017-11-20
      • 2023-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-21
      相关资源
      最近更新 更多