MakeLab实验报告

一曲冷凌霜 提交于 2019-12-03 15:46:06

Lab_4实验报告

屏幕截图

考察内容

本次lab的考察内容为链接的有关知识(其实感觉更像是在传授技巧?),包括通过Makefile,利用g++或ld命令实现静态/动态链接。

解题思路

Task0

目标任务

编写Makefile文件的同时修改main.cpp,将main.cpp编译为可执行文件main。

思路

首先观察各份代码,理清程序思路。main函数的思路是调用三个函数:testPrint(),testPrint(num),notATest()。其中前两个函数在some.h中声明,在some.cpp中定义;最后一个函数在some.cpp中定义。因为main函数只能使用some.h中声明的函数,因此notATest不能被调用。应注释掉该行代码。随后在Makefile中键入以下代码:

main:main.cpp
g++ -o main main.cpp some.cpp

运行make main,成功。

思考题

Q1:代码中的错误是什么?由此看来.h与.cpp的关系是怎样的?

A1:错误是main函数调用了.cpp文件中定义的函数。.h和.cpp的关系类似包含关系,链接器在遇到未声明函数时总是先在.h里找声明,随后在.cpp里找对应函数的实现。

Task1

目标任务

编写Makefile文件,将main0.cpp编译为main0,将main1.cpp编译为main1,当编译目标为main1时,提供以下形式的“调试开关”选项:

make main1 debug=True

思路

不考虑“调试开关”选项,这个Makefile文件和Task0没有太大区别。只是编译目标从一个变成了两个。

程序思路是:main0/1.cpp都调用了function0/1.h中声明的函数,这些函数在实现过程中又都调用了shared.h中的函数。因此makefile语句应为:

main0:main0/1.cpp function0.cpp function1.cpp shared.cpp
g++ main0/1.cpp function0.cpp function1.cpp shared.cpp -o main0/1

现在考虑加入“调试开关”。在Makefile文件开头定义变量debug=False,随后在main1的入口处键入ifeq语句:

ifeq ($(debug),True)
    g++ main1.cpp function0.cpp -DDEBUG function1.cpp shared.cpp -o main1
else
    g++ main1.cpp function0.cpp function1.cpp shared.cpp -o main1
endif

结果运行后总是出错,故障为:

/bin/sh: 1: Syntax error: word unexpected (expecting ")")

随后百度查询到类似情况,原因是ifeq似乎不支持缩进(……),于是去除缩进后再次运行,成功。

思考题

Q1:为什么两个function.h都引用了shared.h而没有出问题?

A1:因为链接器在链接之前,编译器先将function0/1.cpp编译为独立的.o文件,其中各自包含了shared.h中需要调用的函数,因此在链接时没有出问题。

Q2:如果把shared.h中注释掉的变量定义取消注释会出什么问题?为什么?

A2:会报错:"'FOO[abi:cxx11]'被多次定义"。原因在于function0/1.h和shared.cpp都包含了shared.h,即shared.h在没有添加#ifnedf的情形下被重复包含了,因此变量被重复定义,导致链接器报错。

Q3:通常使用shared.h中另外被注释掉的宏命令来规避重复引用的风险,原理是什么?取消这些注释之后上一题的问题解除了吗?不管解没解除背后的原因是什么?

A3:原理是通过宏命令定义判断一个.h文件是否已经被包含过,避免重复包含。取消注释之后问题未能解除。查询资料\(^{[1]}\)得知原因为function0/1.o中均包含了shared.h中变量定义的部分,因此虽然shared.h没有被重复包含,变量依然被重复定义了。

Task2

目标任务

将A文件夹和C文件夹中的代码编译为静态链接库,与B文件夹中已经生成的静态链接库一起和main.cpp编译链接为可执行文件。

思路

在Github\(^{[2]}\)上复习了一下gcc编译为.o文件的指令,将其分别键入A文件夹和C文件夹的Makefile文件中。

在Task2文件夹中编写Makefile文件,利用提示中的cd && make 指令调用A/C文件夹中的make指令。结果发现我错误理解了cd&&make指令的意思:我原以为执行指令后终端仍然停留在子文件夹中,结果发现终端执行后自动跳转回原文件夹。结合不能修改的make clean中删除的是A/libA.a,因此在ar指令中需要相应地改为<A/libA.a>。

随后调用g++指令将三个静态链接库和main.cpp编译链接为可执行文件。

思考题

Q1:若有多个静态链接库需要链接,写命令时需要考虑静态链接库和源文件在命令中的顺序吗?是否需要考虑是由什么决定的?

A1:多次尝试后发现需要考虑源文件和静态链接库之间的顺序,源文件应放在其依赖的链接库的前面。根据CSAPP,这是由于链接器本身的工作原理决定的。此外,不需要考虑静态链接库内部的顺序,无论静态链接库之间是否有依赖关系。这个现象的原因我不太清楚。

Q2:可以使用size main命令来查看可执行文件A所占的空间,输出结果的每一项是什么意思?

A2:

text data bss dec hex filename
23000 744 288 24032 5de0 main

text指储存机器代码的部分占内存的大小;data指初始化的全局和静态变量占内存的大小;bss指未初始化的全局和静态变量占内存的大小;dec和hex为前三者之和的十进制和十六进制形式;filename为可执行文件名。

Task3

目标任务

将A/C文件夹中的代码编译为动态链接库放在/Task3中,与既有的libB.so一起和main.cpp动态链接编译为可执行文件。

思路

按照提示中的步骤,对Task2中操作进行简单替换和重复即可。

思考题

Q1:动态链接库在运行时也需要查找库的位置,在Linux中,运行时动态链接库的查找顺序是怎样的?

A1:根据查找到的资料\(^{[3]}\),动态链接库的查找顺序为:

  1. 编译目标代码时指定的动态库搜索路径
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
  4. 默认的动态库搜索路径/lib
  5. 默认的动态库搜索路径/usr/lib

Q2:使用size main查看编译出的可执行文件占据的空间,与使用静态链接库相比占用空间有何变化?哪些部分的哪些代码会导致编译出文件的占用空间发生这种变化?

text data bss dec hex filename
3264 768 96 4128 1020 main

text部分占用空间大幅减少,data和bss部分占用空间变化不大。推测是由于A.cpp中定义的MassSTR在动态链接时未被包含在text中。

Q3:编译动态链接库时-fPIC的作用是什么,不加会有什么后果?

A3:-fPIC的作用是生成与位置无关的代码。即在Makefile中生成的.so文件中,相关数据对象不使用绝对地址,而是相对地址。这样在动态链接时.so文件可以方便地被插入到内存的任意位置。如果不加-fPIC的话,.so文件使用绝对地址,则在每次链接时会对其中绝对地址进行重定位,生成一份副本,失去了动态链接的意义。

Q4:现在被广泛使用的公开的动态链接库如何进行版本替换或共存?(选做)

Task4

目标任务

在Makefile文件中通过ld命令对Task0的代码进行手动链接。

思路

看了看CSAPP,发现上面关于具体操作的内容似乎只有一句话:

ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o

所以这个[system object files and args]是什么呢……我就直接求助百度了。

百度上关于ld手动链接的内容不是很多,其中看起来有用的大多数也是建议使用g++ -v然后将输出参数拷贝到终端/Makefile文件中。最后在stackoverflow\(^{[4]}\)上找到了比较详细的解答。

这个解答的大意就是在寻常g++指令的末尾添加-v参数,然后在输出信息中找到包含collect2(因为collect2就是ld命令的别名)的一行,将collect2之后的内容拷贝到编辑器中,用"\"分割修饰一下之后运行。这个作者还尝试着删去了一些他觉得无关紧要的部分,然而我并不清楚这个技巧,于是就机械复读了。

随后发现运行时出现了遗漏分隔符的现象,百度之后发现是因为Makefile的缩进格式要求非常严格,我之前用编辑器编辑时为了美观,一次缩进用了两个Tab,纠正之后运行成功。

思考题

Q1:C runtime是什么,主要负责什么工作?(选做)

Q2:动态链接器一个操作系统只需要一个吗?为什么?(选做)

Reference

  1. C++头文件中为何添加了#ifndef #define #endif还会出现变量重复定义的问题
  2. 编译过程与GCC命令
  3. 静态链接与动态链接库的查找顺序
  4. How to link C++ object files with ld
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!