zoukankan      html  css  js  c++  java
  • CSAPP_MakeLab实验报告

    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
  • 相关阅读:
    从零开始学习Sencha Touch MVC应用之六
    从零开始学习Sencha Touch MVC应用之七
    从零开始学习Sencha Touch MVC应用之四
    从零开始学习Sencha Touch MVC应用之四
    从零开始学习Sencha Touch MVC应用之七
    从零开始学习Sencha Touch MVC应用之五
    从零开始学习Sencha Touch MVC应用之三
    JpGraph
    thinkphp RBAC 详解
    PHPExcel常用方法总结
  • 原文地址:https://www.cnblogs.com/XSC637/p/11802693.html
Copyright © 2011-2022 走看看