[Linux] 系统编程原理详解


通过前几篇博客的学习,相信大家已经对Linux系统有了一个初步的认知和了解,那么本篇博客就对所学内容进行有机整合,进入Linux系统编程的阶段.

  • 前面讲过在Linux环境下进行编程需要以下 4 个工具:
  1. 代码编辑器 vim
  2. 编译器 gcc
  3. 调试器 gdb
  4. 工程管理工具 makefile

vim已经有专门一篇博客讲过它的用法与内容了,所以这次主要介绍其他三位主角,看看他们究竟是通过怎样的操作实现编程的.
   [ 这里演示的编程操作以C语言为例 ]

整体步骤

1. 创建文件

输入指令创建 :

vim hello.c	

注: hello.c是你希望创建文件的文件名
在这里插入图片描述

2. 键入代码

hello.c文件中i进入插入模式,输入好程序代码esc , :wq保存退出
在这里插入图片描述

  • 注: 这里不能像在Windows系统中一样,为了使打印结果不一闪而逝加入的system("pause");这一语句,因为Linux系统中不支持.

3. 编译

通过gcc + 目标编译文件名,完成对其的编译,正常编译完成没有反馈
在这里插入图片描述
  这里编译成功之后,我们可以额外的通过ll先查看一下编译产生的文件
在这里插入图片描述   我们可以看到,这个a.out文件就是默认编译产生的文件

4. 执行

通过相对路径方式执行这个a.out文件,打印结果就顺利显示在界面上了:

./a.out

在这里插入图片描述

如果你完成了上述操作,恭喜完成了第一次Linux系统环境下的编程.
有了第一次成功,相信以后多样的代码都可以在Linux环境下顺利执行


接下来,我们深层次地理解一下编程过程中的这些工具与文件:

  • a.out是什么?
     我们通过指令file a.out详细查看一下这个文件:
    在这里插入图片描述
    通过结果我们大致可以得知:
      它是一个Linux下的标准ELF格式文件,并且executable说明它是一个可执行文件,所以我们才可以通过./的方式直接执行它

  • 为什么名为a.out,可以有其他名称吗?
    我们可以在gcc指令编译时就设置好 输出文件的文件名,如下:

gcc hello.c  -o  hello

-o意为- output,后面接 希望输出文件修改的文件名
  我们ls罗列了一下文件夹中的文件,发现确实是通过指令进行了名称修改
在这里插入图片描述
用了这么多次的gcc到底是什么?


gcc

它是一个编译器
GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言

现已被大多数类Unix操作系统(如LinuxBSDMac OS X等)采纳为标准的编译器,GCC同样适用于微软的Windows.
GCC 通过快速扩展,变得可处理 C++。后来又扩展能够支持更多编程语言,如FortranPascalObjective-CJavaAdaGo以及各类处理器架构上的汇编语言等

通过gcc我们可以把编译过程拆解开

● 编译的四个最核心的步骤/整体过程:

  1. 预处理
     a). 宏替换
      相当于文本替换,相当于字符串的复制粘贴.
      如果在程序首部#define SIZE 8,就会将整个文件中的SIZE在预处理阶段全部替换为8
     b). 去注释
      对部分代码进行解释的注释是没有必要编译的,它仅供提高代码可读性
     c). 条件编译
       避免重复编译,提高代码效率
       [https://mp.csdn.net/mdeditor/85244894#
     d). 替换头文件
        将包含的头文件复制引入函数,包括运用到的函数/模块等等
       
    我们修改一下代码,在其中加入宏定义,如图所示,看看预处理之后的效果是什么样子

在这里插入图片描述

输入指令:

gcc -E  hello.c  -o  hello.i				//大写E
  • -E是预处理指令
  • hello.i就是对于源文件预处理产生的文件

回车之后没有反馈,我们vim查看一下这个hello.i文件内部
在这里插入图片描述
可以看到里面相比于之前寥寥几行的代码主体多出了很多行代码,其实这个就是对头文件进行了替换
这里当时的SIZE也直接变成了8,说明确实是在编译之前就进行了文本替换,一个头文件、宏的展开会产生大量的代码。

  1. 编译
    【将c语言代码文件变成汇编代码,也是高级语言向机器语言过渡的一个中间环节】
      输入指令:
gcc -S  hello.i  -o  hello.s				//大写S

hello.s文件保存了我们代码的汇编结果,我们再次vim查看内部
在这里插入图片描述
我们发现已经有些阅读天书的感觉了,但是仍旧有些字段我们是可读的

  • 注:不同的CPU支持的汇编语句、机器指令是有差别的
  1. 汇编
    【将汇编代码变成二进制的机器码】
      输入指令:
gcc -c  hello.s  -o  hello.o				//小写s

vim查看,我们发现现在相对于前两次查看是彻底看不懂了,它是二进制的机器语言,大部分人类看不懂,但是机器可以看懂

  1. 链接
    【把多个.c文件编译成的机器码进行汇总,比如函数在a中定义,并在b中调用,那么不进行链接就无法正常执行】
      输入指令:
gcc  hello.o  -o  hello		

这次终于对前面文件没有依赖了,直接运行指令就可以完成链接

我们ls查看一下当前文件夹在过程中产生的文件:
在这里插入图片描述
发现红色框选的是演示过程中依赖的文件,而最后产生了蓝色的可执行的hello文件

  • 是否和篇头的编译过程一样呢?
    我们一样使用./ + 文件名进行验证,正确执行,说明二者等效。
    在这里插入图片描述
    如上图,正确打印出我们开头设置的宏定义参数,至此,对编译过程的演示就告一段落了

  • 小结
    简单的记忆方法:我们使用的预处理指令顺序为E --> S --> c,相当于esc键,位于键盘左上角的键

这里只是罗列几个常用命令 , 更多gcc指令后缀请客官自行使用man手册查看.


gdb

gdb是一个命令行版本的调试工具,相比于VS等的调试器没有图形界面,无法直观的获取变量值得变化与压栈情况,所以是没有那么容易入手的

这个调试工具相比于VC、z的优点是具有修复网络断点以及恢复链接等功能,比BCB的图形化调试器有更强大的功能。
所谓“寸有所长,尺有所短”就是这个道理

Visual Studio中调试的核心思想在gdb中也适用:

  1. 打断点
  2. F5继续执行

这样的按下快捷方式的调试方式与gdc是有些不同的,gdb是通过许多指令(命令行)来完成调试的。以下演示开始:

  • 要想进行调试,其实是针对最终的可执行程序进行调试,所以需要先进行编译
  1. 编写程序
    我在这里举例创建一个main.c文件,vim进入后编写完成代码
    在这里插入图片描述
  2. 输入指令,进入gdb界面:
gcc main.c -o main  -g				//Debug版本
gdb main

与Debug版本同级的还有release版本,有兴趣可以了解一下

输入完指令就进入gdb的界面了
在这里插入图片描述

  1. break + 行号/函数名 在此行或者函数首打上断点
    在这里插入图片描述
    这里我输入了break + Add函数名,所以调试器告诉我在第8行的Add函数已经被打上断点了。
    例如想要在代码第10行打上断点,可以缩写为b 10

那第8行到底在哪儿?
为了方便起见,给大家一个小建议,打开两个终端,放置于右边窗口
这样你就可以看着代码进行调试了,十分方便直观。
在这里插入图片描述

  1. info break 查看当前断点
    在这里插入图片描述
    调试器显示当前只有一个我打上的断点,是位于main.c文件的第8行的。

  2. del + 断点的编号 删除已有断点
    断点编号就是上图Num所对应的数字,例如所属与它的1

  3. run 代码从头开始运行,触发断点就会停止,缩写为r
    (输入gdb,被调试的程序其实还未开始执行,当输入run才真正开始执行)
    在这里插入图片描述
    由上图我们可以得知,触发了我们所打在第8行的Add函数上的断点

  4. continue让程序继续运行,缩写为c
    程序遇到断点,就会被停止下来。如果想要继续运行就输入指令continue

VS 中的F5相当于同时具备了 run + continue
gdb 中就将而二者分开了

  1. print + 变量名 查看当前情况变量的信息,缩写为p
    在这里插入图片描述

  2. list 展示当前语句附近代码,缩写为l
    笔者觉得有输入这种专门显示附近代码的指令的时间不如再打开一个终端来的直接,一劳永逸啊~

  3. bt 查看当前调用栈
    (例如:递归函数的调用栈)
    在这里插入图片描述
    这里bt后显示内容说明main函数调用了Add函数

  4. next 单步执行,一次执行一行语句,缩写为n,相当于VS中的F11


Makefile

make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。
Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。
Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。

  • Makefile文件中主要包括三个主要的部分:
    1.target 目标 想要生成的可执行程序(文件、库等),相当于输出
    2.dependence 依赖 (为了生成可执行文件需要用到的文件),
    相当于输入,允许没有依赖。
    3.conmmand 命令 具体动作,为了生成目标才去的操作
  1. vim Makefile 编写Makefile文件,必须是这个名称!
  2. 填写三个部分,保存退出
    在这里插入图片描述
  3. 执行make指令进行编译,等价于make hello
  4. 如果make之后没有对象,默认执行第一个目标如果make之后没有对象,默认执行第一个目标

异常分析 : 输入make之后,出现遗漏分隔符错误
在这里插入图片描述
反复修改没有解决问题,编辑文件时gcc,rm前面使用的是Tab分隔符。
最后使用linux自带的编辑器gedit打开Makefile,将vim下的Tab键换为gedit的Tab键,得到正确结果。

总结遗漏分隔符问题解决方法:

  1. gccrm之前一定要有一个tab分隔符,不能使用空格。

  2. 使用vim编辑文件,如果配置文件vimrc中有set expandtab(使用空格代替制表符) ,则也会出现遗漏分隔符问题。因此最好将这条语句注释,或者使用gedit编辑器重新编辑makefile

注:make也属于增量编译,如果对代码有改动,最后操作时间改变,再次执行make指令就不会显示 xx文件 是最新的了。

  • 另外在Makefile中添加clean项,其中包含删除某文件语句,保存退出
    执行make clean就会进行工程清理的工作,把生成的中间文件都删除掉,只保存最纯粹的源代码。

  • 小结
    Makefile还是比较经典的工程管理工具,但平常使用时不太会手写,而是运用一些工具来生成:

  1. cmake
  2. bazel (中文名:火焰刀)Google出品

猜你喜欢

转载自blog.csdn.net/qq_42351880/article/details/85242725