美文网首页
c++ 学习笔记1——理解 gcc 编译和链接

c++ 学习笔记1——理解 gcc 编译和链接

作者: 落撒 | 来源:发表于2020-11-12 23:53 被阅读0次

    声明和定义

    首先来说两个概念,声明和定义(此处仅针对函数)。

    声明,可以简单理解为说我们有这样一个函数;

    定义,可以简单理解为这个函数要作什么,是怎么实现的。

    先来一个小例子,创建 main.cpp 文件

    #include <iostream>
    
    // 声明 add 方法
    int add(int, int);
    
    int main()
    {
        printf("add(3, 4) = %d\n", add(3, 4));
        return 0;
    }
    
    // 定义 add 方法
    int add(int a, int b)
    {
        return a + b;
    }
    

    很简单,其实就是在最上面声明了一个 add 方法,最下面对该方法进行定义,然后就可以通过执行 main 函数,完成内部对 add 方法的调用。

    如执行如下命令:

    ➜  g++ main.cpp
    ➜  ./a.out
    add(3, 4) = 7
    

    这里通过 g++ 命令通过源代码生成了可执行文件,接下来看一下从源代码到可执行文件的过程。

    编译和链接

    直接说结论,从源代码到可执行文件,中间存在两个过程,分别为 编译链接

    编译

    g++ 命令将两个过程合在了一起,为了更清晰的认识两个过程,我们将其过程拆解,具体如下:

    ➜  g++ -c main.cpp 
    ➜  ls
    main.cpp  main.o
    

    ➜ g++ -c

    -c -- Compile and assemble, but do not link

    通过 g++ -c 我们生成了一个 main.o 的对象文件,其实这个对象文件中的内容就是我们对一些函数的定义。我们使用 nc -C 命令查看其中的内容:

    ➜  hello nm -C main.o
                     U __cxa_atexit
                     U __dso_handle
                     U _GLOBAL_OFFSET_TABLE_
    0000000000000096 t _GLOBAL__sub_I_main
    0000000000000000 T main
                     U printf
    0000000000000031 T add(int, int)
    0000000000000049 t __static_initialization_and_destruction_0(int, int)
                     U std::ios_base::Init::Init()
                     U std::ios_base::Init::~Init()
    0000000000000000 r std::piecewise_construct
    0000000000000000 b std::__ioinit
    
    

    我们看到的内容就是程序中所用到的函数,主要关注我们显示用到的函数 main、printf、add,前面有数字的就代表该函数的定义是储存在该对象文件中,对应此处的 main、add。

    回过头来,编译阶段作了什么呢?其实编译阶段仅仅只是作了语法检查的工作,还句话说,只要我们的代码没有语法错误,就可以编译通过。

    可以通过删除 add 函数的定义来验证。可以明确知道的是,删除之后,代码是没有办法正常工作的,但是能否通过编译呢?我们可以执行 g++ -c 来验证。

    我们先将 add 函数的定义注释掉。

    // int add(int a, int b)
    // {
    //     return a + b;
    // }
    

    然后执行 g++ -c

    ➜  ls
    main.cpp  main.o
    ➜  rm main.o 
    ➜  ls
    main.cpp
    ➜  g++ -c main.cpp
    ➜  ls
    main.cpp  main.o
    ➜  nm -C main.o 
                     U __cxa_atexit
                     U __dso_handle
                     U _GLOBAL_OFFSET_TABLE_
    000000000000007e t _GLOBAL__sub_I_main
    0000000000000000 T main
                     U printf
                     U add(int, int)
    0000000000000031 t __static_initialization_and_destruction_0(int, int)
                     U std::ios_base::Init::Init()
                     U std::ios_base::Init::~Init()
    0000000000000000 r std::piecewise_construct
    0000000000000000 b std::__ioinit
    

    可以明确看到,g++ -c 的操作成功色和功能成了 main.o 对象文件,该文件内部有了变化,add 函数前面的数字没有了,该函数的定义不在此对象文件中了。验证了我们所说的编译阶段就是语法检查。

    我们知道了前面带有数字的 main 函数在该对象文件中被定义,那没有数字的 printf 以及 add 函数的定义在哪呢?显然如果需要让程序能够使用,我们就需要提供这些(printf 在标准库中已经提供),当然不必一定要在同一个对象文件中提供,我们尝试在外部提供,让链接 这个过程帮我们将两个对象文件合到一起。

    链接

    创建一个 other.cc 文件

    int add(int a, int b)
    {
        return a + b;
    }
    

    对 other.cc 进行编译

    ➜  ls
    main.cpp  main.o  other.cc
    ➜  g++ -c other.cc
    ➜  ls
    main.cpp  main.o  other.cc  other.o
    ➜  nm -C other.o
    0000000000000000 T add(int, int)
    

    接下来就可以通过链接将两个 .o 文件链接到一起,输出可执行文件。

    ➜  g++ main.o other.o
    ➜  ls
    a.out  main.cpp  main.o  other.cc  other.o
    ➜  ./a.out
    add(3, 4) = 7
    

    至此就完成了编译、链接两个过程。

    总结

    本文通过编译链接的两个过程,加深了对声明定义的理解。

    简单可以这样概括

    编译:检查语法错误

    链接:将声明和定义链接起来

    用到的命令
    g++ -c 编译

    nm -C *.o 查看 *.o 文件内容
    g++ *1.o *2.o 将多个.o对象文件进行链接

    在执行 g++ 命令的时候,我们常会出现这样的错误。

    ➜  g++ main.cpp          
    main.cpp: In function ‘int main()’:
    main.cpp:7:32: error: ‘add’ was not declared in this scope
        7 |     printf("add(3, 4) = %d\n", add(3, 4));
          |    
    
    ➜  g++ main.cpp
    /usr/bin/ld: /tmp/ccR71J1A.o: in function `main':
    main.cpp:(.text+0x13): undefined reference to `add(int, int)'
    collect2: error: ld returned 1 exit status
    

    以及

    ➜  g++ main.cpp other.cc
    /usr/bin/ld: /tmp/cc0XOMFk.o: in function `add(int, int)':
    other.cc:(.text+0x0): multiple definition of `add(int, int)'; /tmp/ccWGLjai.o:main.cpp:(.text+0x31): first defined here
    collect2: error: ld returned 1 exit status
    

    现在已经可以更为深刻的理解这几个错误的意思了。

    相关文章

      网友评论

          本文标题:c++ 学习笔记1——理解 gcc 编译和链接

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