• 【Linux】--make/makefile--gcc/g++/gdb


    make/makefile

    概念

    make是Linux下的一条指令,而Makefile是一个文件,可以简单理解为make就是执行Makefile里面写的目的。

    会不会去写Makefile可以侧面的看出一个人是否具备完成大型工程的能力

    假设现有一个工程里面有上千上万个源文件,那么就可以使用Makefile定义一系列的规则来指定,哪些文件需要先编译,哪些文件后编译。当我们写好Makefile后,只需要一个make命令就可以让整个工程完全自动编译,极大地提高了效率

    也就是说,make是一个解释Makefile中指令的命令工具,两者搭配使用就可以完成项目自动化构建

    Makefile

    那么Makefile应该怎么样去写呢,那就得先了解一下Makefile的原理了。

    在Makefile里面我们需要去定义一个方法时,就得考虑到两个因素,依赖关系依赖方法。举例:当我们编写好一个源文件时,我们需要对它进行编译生成可执行文件,那么可以把这个可执行文件看做是目标文件,想要生成它就得依赖我们写好的源文件,那么我们就可以把源文件看做是依赖文件。那有了源文件后,我们需要通过编译工具编译的方法才能生成可执行文件,那这个方法我们就可以看做是依赖方法。
    在这里插入图片描述

    首先我写好了一个.c源文件,并且创建了一个makefile文件,按照之前学的,这是我们可以用gcc直接去编译生成可执行文件,但是如果每一次都去写gcc的指令就会很麻烦,因此我们可以考虑将这条指令放进makefile里面,我们只需要执行一下make指令就可以自动调用了。

    image-20221029150244348

    编写的格式就跟上面所讲,需要有依赖关系、依赖方法。目标文件的后面必须要有跟上一个冒号,并且依赖方法另起一行必须以Tab键开头,不能是几个空格

    当我们写好之后,make一下看看效果

    image-20221029150538443

    可以看到当我们make一下后,系统会自动执行在makefile里面的指令,生成一个可执行文件。但是现在当我们再make一下,还会再次执行吗?

    image-20221029150712364

    报错了,这是为什么呢?

    make指令这点就带来了好处,当我们的源文件没有被修改时并且可执行文件已经生成时,make就会禁止你的再一次编译,那么问题来了make怎么知道源文件有没有被修改呢,这就得说到文件的属性了。

    image-20221029151230977

    当我们去查看文件的属性时会发现,文件都会有三个时间属性,那么这三个分别代表什么呢

    1. Access:文件最近一次被访问的时间
    2. Modify:文件最后一次被修改的时间
    3. Change:文件的状态被修改的时间

    当我们的源文件没有被修改时并且可执行文件已经生成时,此时我们在make一下,make会去比较源文件和已有的可执行文件的被修改时间,如果可执行文件的比源文件的要晚,那么make不再执行编译指令,反之同理

    那如果我就想让它执行呢,这时候就引入了一个makefile中的一个关键字:.PHONY(伪目标,该目标总是被执行)

    image-20221029151900911

    image-20221029151918638

    可以看到此时,该条指令就可以无限执行了。

    make

    make是如何工作的呢

    1. make首先会在当前目录下找“makefile”或“Makefile”的文件
    2. 找到之后,它会找文件中的第一个目标文件,并把该依赖方法执行生成最终的目标文件
    3. 假设现在目标文件为hello,依赖文件为hello.o,但是此时目录下没有hello.o文件,那么make就会继续往下找以hello.o为目标文件的依赖关系,以此类推。
    4. 也就是说make会一层一层的去找文件的依赖关系,直到最终编译出第一个目标文件
    5. 如果最后被依赖的文件找不到,那么make会直接退出并报错

    gcc/g++/gdb

    gcc/g++

    众所周知,当我们使用C/C++语言写好代码后,我们的先让源文件经过预处理,编译,汇编,链接几个步骤后才会生成一个二进制机器语言,这样计算机才能看得懂并且执行。那么在Windows下我们可以利用集成开发软件去帮我们完成这几个步骤,同理在Linux下我们就需要借用gcc/g++工具来帮我们完成。

    预处理

    预处理的主要功能包括宏定义,文件包含,条件编译,去注释等操作

    我们在编写代码时包含的头文件就会在这个步骤进行展开,宏定义会进行替换,我们写的注释也会在这个步骤去除

    在Linux中该步骤生成的文件一般以.i为后缀,我们可以通过命令去完成这个步骤,需要用到-E选项

    gcc -E main.c -o main.i
    
    • 1

    编译

    这个步骤,首先gcc/g++会检查代码是否有错误,以及确定代码的实际要做的工作,如果一切正常无误后,gcc/g++就会把代码翻译成汇编语言

    在Linux中该步骤生成的文件一般以.s为后缀,我们可以通过命令去完成这个步骤,需要用到-S选项

    gcc -S main.c -o main.s
    
    • 1

    汇编

    汇编阶段就是把生成的.s文件转为目标文件也就是生成机器可以识别的二进制语言

    在Linux中该步骤生成的文件一般以.o为后缀,我们可以通过命令去完成这个步骤,需要用到-c选项

    gcc -c main.c -o main.o
    
    • 1

    链接

    这个阶段,目标文件会和系统库进行连接生成一个可执行程序

    Linux中库的命名:去掉前缀lib,去掉后缀.so,剩下的就是库的名称

    静态链接

    这个链接方式下,代码会将静态链接库拷贝一份到最终的可执行程序中,这样虽然提高了效率但是会是可执行程序的空间变得很大,不利于大型项目

    在linux下想要生成静态链接就要在后面加上 -static选项,并且要确保已经安装了C/C++的静态库

    sudo yum install glibc-static//安装C静态库
    sudo yum install libstdc++-static//安装c++静态库
    
    • 1
    • 2

    image-20221029155826995

    image-20221029155930787

    动态链接

    这个链接方式下,在可执行程序执行的时候,会将动态链接库映射到一块虚拟空间中,然后根据需要再去找对应的函数代码,这样可以极大地缩小可执行程序的空间。

    Linux下默认的链接方式是动态链接,依赖的动态库一般为.so为后缀

    image-20221029155056579

    image-20221029155131557

    对比上面的静态链接明显大小小很多

    选项操作

    1. -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
    2. -S 编译到汇编语言不进行汇编和链接
    3. -c 编译到目标代码
    4. -o 重命名可执行文件
    5. -static 此选项对生成的文件采用静态链接
    6. -g 生成调试信息。GNU 调试器可利用该信息

    gdb

    在平时Windows下我们使用想vs2019这样的话编译器时都会有一个调试的功能,让我们发现代码的错误之处。

    同理在Linux下也有,这个调试工具就叫gdb,首先我们要安装一下gdb

    sudo yum install gdb
    
    • 1

    安装好之后当我们直接运行gdb调试我们的可执行代码时会发现出问题了

    image-20221029161043362

    因为程序会分为两个版本,一个是Debug版,一个是Release版

    在debug模式下我们才可以对代码进行调试,但是Linux默认的模式是release,所以在生成可执行文件时需要加上-g选项,让它生成的可执行程序是debug模式才可以进行调试

    image-20221029161431544

    自此我们才算是正式进入到了gdb中

    那么对于gdb而言也有属于自己的指令

    1. list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
    2. list/l 函数名:列出某个函数的源代码。
    3. r或run:运行程序。
    4. n 或 next:单条执行。
    5. s或step:进入函数调用
    6. break(b) 行号:在某一行设置断点
    7. break 函数名:在某个函数开头设置断点
    8. info break :查看断点信息。
    9. finish:执行到当前函数返回,然后挺下来等待命令
    10. print§:打印表达式的值,通过表达式可以修改变量的值或者调用函数
    11. p 变量:打印变量值。
    12. set var:修改变量的值
    13. continue(或c):从当前位置开始连续而非单步执行程序
    14. delete breakpoints:删除所有断点
    15. delete breakpoints n:删除序号为n的断点
    16. disable breakpoints:禁用断点
    17. enable breakpoints:启用断点
    18. info(或i) breakpoints:参看当前设置了哪些断点
    19. display 变量名:跟踪查看一个变量,每次停下来都显示它的值
    20. undisplay:取消对先前设置的那些变量的跟踪
    21. until X行号:跳至X行
    22. breaktrace(或bt):查看各级函数调用及参数
    23. info(i) locals:查看当前栈帧局部变量的值
    24. quit:退出gdb

    接下来我们一个一个看

    首先显示我们的代码,需要用到l指令

    image-20221029161748856

    该指令后面也可以加上行号或者函数名,显示指定内容

    接下来我们需要为程序设置一个断点,如果不设置直接执行程序就会跑完,这是要用到b + 行号指令,设置好之后我们可以通过info b查看我们已经设置好的断点

    image-20221029162144207

    还可以通过d + 序号删除对应的断点,注意这里的序号不是行号,而是我们设置好断点后每个断点通过info b查看之后每个断点对应的序号

    image-20221029162403468

    设置好断点后此时我们就可以运行起来了,输入r运行程序(对应VS2019F5),如果执行到某行,该行有函数我们想进入函数调试就需要输入s(对应vs2019的F11),不想进入函数就输入n(对应vs2019F10)

    image-20221029163241939

    image-20221029163327344

    那么现在如果我进入到函数里面了,但是函数里有千次的循环怎么直接执行完函数退出呢,需要输入finish

    image-20221029163515058

    那么当我们进入到一个函数时,我们查看函数的变量值和变量的地址,这时候需要输入p + 变量名p + &变量名

    image-20221029163746214

    如果想每次执行完一条语句就显示出想查看的变量值,就需要用display + 变量名,不想看了想删除掉就使用undisplay + 变量名

    image-20221029164018697

    剩下的一些可以自己去探索了,在上面所讲的都是一些比较常用的。

    总结

    学习到这里已经是可以再Linux下进行代码编写了

    接下来就得学习一些系统底层的知识了

    每天进步一点淡👍

  • 相关阅读:
    Java Math
    基于springboot小型车队管理系统毕业设计源码061709
    深度学习框架pytorch:tensor.data和tensor.detach()的区别
    uniapp如何实现关闭前面指定数目页面
    JAVA使用SnakeYAML解析与序列化YAML
    Java后端入职第四天,就被要求代码回退(Git回退实战)
    python趣味编程-5分钟实现一个蛇梯游戏(含源码、步骤讲解)
    python自动化测试全栈之ApiFox批量运行脚本生成报告
    python代码优化学习
    常见面试题之计算机网络
  • 原文地址:https://blog.csdn.net/weixin_52563203/article/details/127589027