C和C++入门——编译与链接


笔者前段时间负责了部门应届生的面试工作,在面试过程中,发现很多学生C/C++应用较多,甚至参与过一些大的软件项目的开发,但是却缺乏理论,希望本系列能够为即将毕业的同学们提供帮助。

——本文会持续更新,今天先写这么多。

目录

C++的编译和链接

1 简单的hello word程序

2 Hello word的ELF文件

3 多个C++文件链接

内存分布问题

从内存的角度看链接问题

从编译的角度看多态

从内存角度看多态

从内存角度看虚函数

从内存角度看父类与子类的指针



C++的编译和链接

在编程阶段,经常报错的一个最简单的程序,分为编译和链接两部分,本节解释从简单的例子解释这几个概念。

1 简单的hello word程序

老生常谈,继续从hello word说起

#include <stdio.h>

int main(void)
{
    printf("hello word\n");
}

将上述文件保存为一个C++文件,命名为HelloWord.cpp,然后使用g++编译

gcc heloword.cpp

a.out

输出为:

hello word

上述命令较为简单,从helloword.cpp文件直接生成了可执行文件a.out,没有展示编译的具体过程,一个编译过程分为预处理、编译、汇编、可执行文件生成几个步骤,对过程感兴趣的,可以分别使用如下命令:

gcc -E helloword.cpp >helloword_Extend.cpp    //预处理后的文件

gcc -c helloword.cpp                                                      //只执行编译命令,生成.o文件

gcc -S helloword.cpp                                                     //生成汇编文件

也可以按喜好修改生成的可执行文件

 gcc -o helloword.bin helloword.cpp 

2 Hello word的可执行文件

如果需要分析一个可执行文件,我们一般使用readelf命令,如使用readelf -h a.out读取文件头:

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              DYN (共享目标文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x530
  程序头起点:          64 (bytes into file)
  Start of section headers:          6448 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       56 (字节)
  Number of program headers:         9
  节头大小:         64 (字节)
  节头数量:         29
  字符串表索引节头: 28

ELF文件的头部Magic存储了一组数字,称为魔数,有兴趣的可以搜索魔数相关的内容

3 多个C++文件链接

很多学生在回答这个问题的时候,可以轻易把概念说出来,对该问题的来源、原理、结果却没有相应的认识,多文件链接,首先解决的是,模块间的协作问题,这个协作,一是让别的模块知道本模块对外提供的数据和接口(变量、函数、宏定义等),二是在最终生成的文件中,能找到相应元素,找到相应符号,这两个过程分别对应到编译和链接阶段。

我们把helloword问题路略微作一个扩展,准备两个文件,1.cpp和linkTest.cpp

1.cpp:

#include <stdio.h>

void func1(void)
{
    printf("This is 1.c func1\n");
}

linkTest.cpp:

#include <stdio.h>

extern void func1(void);
int main(void)
{
    printf("Call func1\n");
    func1();
}

gcc 1.cpp linkTest.cpp -o linkTest

linkTest输出

Call func1
This is 1.c func1

上述过程,是一个最简单的跨文件函数调用,因为我们有func1函数的源码,所以可以随心所欲的使用,如果因为某些原因,我们不想提供func1的源代码,引申出下面的几个问题:

1、1.cpp文件编译成静态库,如何调用

2、1.cpp文件编译成动态库,如何调用

3、静态库和动态库对于生成的linkTest目标文件到底有什么区别

4、程序执行阶段,main函数是如何找到func1函数的

静态链接问题

执行如下命令

gcc -c 1.cpp

ar -rc 1.a 1.o(ar直接使用cpp生成.a文件可能导致符号表错误)

会生成静态库1.a,这里遇到一个问题:

 使用.a文件链接失败,而使用.o链接成功,从符号表看,二者并无差异

shell@ubuntu:~/test$ gcc 1.a linkTest.cpp 
/tmp/cc8hB75y.o:在函数‘main’中:
linkTest.cpp:(.text+0x11):对‘func1()’未定义的引用
collect2: error: ld returned 1 exit status
shell@ubuntu:~/test$ gcc 1.o linkTest.cpp 

而二者的符号表并无差异:

shell@ubuntu:~/test$ readelf -s 1.a

文件:1.a(1.o)

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 1.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 _Z5func1v
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts
shell@ubuntu:~/test$ readelf -s 1.o

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 1.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 _Z5func1v
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

解决过程1:

上遇到编译过程,首先考虑1.cpp使用了cpp后缀,所以自动按C++的标准进行了编译,修改1.cpp为1.c,重复上述过程,得到新的1.a

shell@ubuntu:~/test$ readelf -s 1.a

文件:1.a(1.o)

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 1.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 func1//注意这个符号的变化
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

func1的名称,由_Z5func1v变成了我们期待的func1,但是并为解决问题。

解决过程2:

考虑符号表查找问题,把1.a和linkTest.c更换顺序,使用如下命令重新比编译:

gcc linkTest.cpp 1.a

运行成功,分析原因,是因为linkTest.cpp中作了

extern void func1(void);

编译器认为完成linkTest.cpp的编译后,会出现func1的具体定义,所以需要1.a出现在linkTest.cpp之后,而使用1.o时,编译器会把.o文件作为编译过程的一部分,将其符号表列入其维护的编译过程的符号表内,这个问题我们后续讲到makefile的时候解决。


内存分布问题

从内存的角度看链接问题

从编译的角度看多态

从内存角度看多态

从内存角度看虚函数

从内存角度看父类与子类的指针

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值