• Linux工具的基本使用


    序言

    这个博客算是其他工具的使用总结吧,里面包含gdb,动静态库的简单认识…其中gdb是我们今天的额难点,需要我们好好的了解一下.

    gcc & g++ 编译器

    这两个工具比较的简单,其中gcc是C语言的编译器的,g++是C++的编译器,我们这里用gcc来演示,它们的选项都是一样的.

    程序编译

    我们先来看看如何在Linux下运行一个C语言的代码.我们看到当前目录下有一个.c文件

    image-20220807150709930

    我们要做的就是编译这个代码.

    [bit@Qkj 08_07]$ gcc main.c 
    
    • 1

    image-20220807150806701

    现在我们就可以看到了如何编译一个代码,可执行程序的文件名默认是a.out,当然我们也可以修改.

    [bit@Qkj 08_07]$ gcc main.c -o mybin
    
    • 1

    image-20220807151007710

    我们都知道,一个代码要变成可执行程序要经过以下若干步骤,我先列出来,在Linux的环境下给大家演示,有些命令看不太懂没有关系,就看文件的变化就可以了

    • 预处理

    • 编译

    • 汇编

    • 链接

    预处理(预编译)

    预处理大致包含四个方面,就是去注释,宏替换,头文件展开和条件编译.这个过程我们在之前就谈过.

    #include       
    #define M 100      
    int main()      
    {      
      // 打印一个宏      
      printf("宏 %d\n",M);      
          
    #ifdef A      
      printf("hello A\n");      
    #else                                                                                                    
      printf("对不起,你没有定义 A\n");      
    #endif      
      return 0;      
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们希望得到预处理后的结果的,下面的选项就可以.

    [bit@Qkj 08_07]$ gcc -E main.c -o main.i
    
    • 1

    image-20220807152103955

    编译

    编译器把代码翻译成汇编语言

    [bit@Qkj 08_07]$ gcc -S main.i -o main.s
    
    • 1

    image-20220807152438735

    image-20220807152555154

    汇编

    将汇编文件翻译成可重定位二进制文件,在Windows下是.obj文件.这时候我们这个文件还不能运行.

    [bit@Qkj 08_07]$ gcc -c main.s -o main.o
    
    • 1

    image-20220807152638087

    image-20220807152712593

    链接

    这个才是我想和大家分享的,链接一直是我们比较难理解的.我们可以从这个角度理解,一旦我们调用了一个函数,如果这是别人实现的函数,例如printf函数,编译器就会问,这个printf的函数在哪里?我们使用的printf如何和C库里面的函数链接出来.

    [bit@Qkj 08_07]$ ls /lib64/libc*
    
    • 1

    image-20220807155300243

    动静态库

    前面我们说了库这个词,这是我第一次正式接触库的概念,我们下载编译器的时候,会自动下载相应的库,我们之前包含的标准库的头文件,为何编译器可以知道头文件所在的路径,原因就是编译器下载的时候,.像库这些文件的路径已经默认确定好了.头文件是包含函数声明,库是函数的实现.

    库分为动态库和静态库,等会我们感性的认识一下它们两个的区别.在Linux, .so后缀就是动态库,Windows下是.dll.静态库在Linux是.a,Windows下是.lib.

    • 动态库 我们只是包含头文件,遇到标准库的函数,就去库里面找 动态链接
    • 静态库 编译器遇到了库里面的函数,编译器自动把这个函数给复制到你的可执行程序中 静态链接

    注意,Linux默认是连接动态库,我们也推荐的是动态库.如果我们想要静态库连接,下面也行,加上一个选项就可以了

    [bit@Qkj 08_08]$ gcc main.c -o s_a.out -static
    
    • 1

    image-20220808085440031

    上面的指令可能跑不过,原因就是Linux默认一般都是动态库,我们需要自己下载静态库,记住在root用户下下载,我们还没有添加sudo这个东西,一个是C库,一个是C++的

    [root@Qkj ~]# yum install -y glibc-static        
    [root@Qkj ~]# yum install -y libstdc++-static
    
    • 1
    • 2

    ldd 指令

    ldd可以观察可执行程序所依赖的库,

    [bit@Qkj 08_08]$ ldd a.out 
    
    • 1

    image-20220808081046599


    gdb 调试器

    gcc/g++都是编译器,gdb才是我们的调试器关于这个调试器,大家知道它的基本的用法就可以了,我们先来用简单的指令.

    debug & release

    在Linux下,默认是release模式,这个模式不可以调试,而且编译器还会做一定程度的优化.gcc 的release 版本是交付给用户使用,debug里面包含了调试信息 ,体积上一定大于release模式.

    使用下面的选项就可以得到debug模式的版本

    image-20220808085949949

    至于多的那一部分,就是调试信息

    image-20220808091227194

    调试

    我们先把代码给放出来,先稍微的看两眼.

    #include     
        
    int add(int a)    
    {    
      int sum = 0;    
      int i = 0;    
      for(i=0;i<a;i++)                                                                                       
      {                                                
        sum += i;                                      
      }                                                
                                                       
      return sum;                                      
    }                                                  
                                                       
    int main()                                         
    {                                                  
      int x = 10;                                      
      int total = add(x);                              
                                                       
      printf("%d\n",total);                            
      return 0;                                        
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们这里开始,直接开始调试.这个还是比较简单的,直接只用这个格式的指令就可以了. gbd debug版本

    [bit@Qkj 08_08]$ gdb mybin_g 
    
    • 1

    image-20220808092338698

    如果你不想调试了,可以直接q,退出调试.

    image-20220808093135375

    显示代码,直接 l

    image-20220808093347713

    它默认不是从第零行显示,如果我们想要从某一行显示,可以l后面跟上数字,注意,一般gdb会自动记录上一个命令,你可以直接按回车执行上一条命令.

    image-20220808093514085

    打断点 b 行号,打断点很重要,我们在第十五行打断点

    image-20220808093915677

    查看断点,info b

    image-20220808094040312

    跳到下一个断点 c

    image-20220808102548179

    取消断点 d 断点编号

    image-20220808095425529

    程序跑起来,他会停留在第一个遇到的断点, 直接 r

    image-20220808094234477

    查看变量,p

    image-20220808094343028

    到下一行 n,可以理解成 逐过程,类似F10,也就是遇到函数不进入.

    image-20220808094411657

    逐语句,s 进入函数 类似F11

    image-20220808094659954

    常显示display对于下面我们总不能每次都p sum的值吧,这里有常显示的指令

    image-20220808101951332

    取消常显示,undisplay,这里面用的是编号

    image-20220808102104475

    查看堆栈 bt

    image-20220808102246212

    until 10 跳到指定行

    image-20220808102354470

    finish 跳出当前函数

    image-20220808102434709


    make和Makefile

    我们在window系统下学习C语言,一般会使用VSCode、VS这些软件,它们有一个统称叫做集成开发环境 ,在Vs2013中我们很容易写出几个 .c 文件,只需要编译一下就可以运行起来,那么在Linux环境下该怎么做?这就是需要make和makefile,这里需要先说明一下,在公司里面makefile是可以自动生成的,不需要我们手写,但是这里需要我们了解原理.

    我这里就先说一下,不做详细的解释

    • make 是一条命令

    • Makefile 是一个文件

    环境准备

    先创建几个文件,里面的内容就随便编写了.

    [bit@Qkj 08_07]$ touch main.c mytest.c mytest.h
    
    • 1

    在这里插入图片描述

    mytest.h

       #ifndef __MYTEST_H__          //#ifndef __MYTEST_H__这个先不用管
       #define __MYTEST_H__  
       #include 
       
       void func();
                                                               
       #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编写mytest.c

    #include "mytest.h"
    void func()
    {
        prinf("func()\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编写main.c

    #include "mytest.h"
    int main()
    {
      show();                                                                
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Makefile/makefile

    在谈make之前,我们需要有谈一下makefile,首先这是一个文件,我们先创建一个这个文件.注意Makefile和makefile都可以.

    我们在本目录下创建一个Makefile(或者makefile)文件

    [bit@Qkj 08_07]$ touch Makefile
    
    • 1

    image-20220807060841109

    那么这里就开始需要谈谈这个文件里面的内容了,我们想是不是可以吧自己想要的指令写道这个文件里面,然后通过某种方法可以简便的做法.那么在里面我们可以这么做.

    mybin: main.c mytest.c
    	gcc main.c mytest.c -o mybin  
    
    • 1
    • 2

    在这里插入图片描述

    这里是我们今天的重点,我会好好的解释的

    在这里插入图片描述

    依赖关系&依赖方法

    我们首先要知道一点:一个Makefile文件存在下面的东西,缺一不可

    • 依赖关系
    • 依赖方法

    那么这俩个究竟是什么东西,我们先来举一个例子.假设在学校里面,你缺生活费了,你给你把打了一个电话,说我是你孩子,所以需要给我打钱.这时候就出现了这两个东西.亲情就是依赖关系,打钱就是依赖方法.

    这里面我们还可以这么做,我们编译程序的过程也可以控制出来.

    image-20220807082610988

    make

    make是一个指令,我们就是简单make一下就可以执行编译程序的功能.

    我们只需要用一下make命令

    image-20220807083151043

    清理解决方案

    我们在VS里面看到过清理解决方案这种情况,在Linux中中也就是删除掉我们编译出来的程序,我们就是下面的做法.

    image-20220807082936423

    这个地方还算是比较好的,如果我们生成的临时文件很多呢?总不能每一次都可以保证自己删除时完全的正确的吧,所以我们也把这个清理的程序放到makefile中.

    这里面还有一点没有谈到的,不过先不要着急,这里面都会和大家讲.

    image-20220807083901690

    image-20220807083830694

    make clean

    现在就存在一个问题,为何事make clean,要知道上面我们编译程序可是一个make就可以了,难道clean有什么是特殊的?准确来说,是第一个依赖关系和依赖方法比较特殊,谁在第一个,make的就是谁,其余的前面都是make+目标文件.

    image-20220807084531163

    image-20220807084600613

    伪目标

    在现实生活中,存在一些孤儿院的孩子,它们不存父母,计算机中也存在相似的东西,这中称之为为目标,所谓伪目标就是没有依赖文件的目标文件.这里需要注意的,伪目标也是目标,不要把伪军不当军队.

    image-20220807085158771

    总是可执行

    现在还存在最后一个大问题,.PHONY是什么,它是必须的的吗?大家可以把**.PHONY**看作makefile里面的一个关键字.

    image-20220807133800489

    一般情况下,我们用.PHONY修饰伪目标,被它总是修饰的总是被执行,那么什么是总是被执行呢?我们先来看看什么是总是不被执行的.

    注意,现在我们的.PHONY只是修饰的clean目标文件,我们先看看什么是总是不被执行的.

    image-20220807135051666

    我们呢观察到,我们make了多次,编译器会告诉我们当前程序已经是最新的了,不需要执行了.这就是总是不被执行的.反之,如果我用.PHONY修饰编译程序的那部分目标文件,他就可以总是被执行了.不过我们不建议这么做,主要后面公司里面一个程序很大,明明已经是最新了的,为何还要重新编译,没必要.

    image-20220807135554786

    一般我们学到基础那里就可以了,要是还要深入的学习,这里可以稍微了解一下,我们可以使用一些符号来表示目标文件和依赖文件

    目标文件 :依赖文件
    
    • 1
    • 冒号左边 目标文件 也就是 $@
    • 冒号左边 依赖文件 也就是 $^

    要是我们想要简省依赖方法就可以写下面的命令

    gcc $^ -o $@
    
    • 1

    image-20220807142327526

    要是我们还想进一步省略,就要写下面的命令,不过这里我们就不建议了,毕竟以后的makefile又不是我们来写的.

    在这里插入图片描述

    解释一下

    • %.o 对应.c文件生成的.o文件
    • %.c 本目录下所有的.c文件
    • $< 所有的.c文件一一展开在gcc下生成对应的.o文件

    stat 指令

    这里面我们需要在看看这个指令,我记得前面好像说过一嘴,没说也没有关系,这里面在说说.statu可以看一下文件的属性,谈这个关键字主要是想看看编译器是如何知道你的代码是最新的?这是一个重点.

    [bit@Qkj 08_07]$ stat main.c 
    
    • 1

    image-20220807140308370

    在Linux中,文件的属性里面一般包含三个时间,我们都知道,文件=内容+属性

    • Access: 进入或读取文件的最新时间
    • Modify: 修改文件内容的最新时间
    • Change: 修改文件权限的属性的最新时间

    也就是说,我们每执行一次操作,都会导致这三个时间的变换.例如我们如果修改一下文件的权限,这样就会导致Change时间被修改.

    image-20220807141051019

    这里我们需要注意的是,如果我们修改文件的内容可能也导致文件的属性被修改,主要文件的大小也是属性的一部分,这一点要记住了.还有就是Access: 时间,在比较新的Linux内核,我们如果读取文件的话可能不会修改Access: 这个时间,主要是这种操作太过频繁,一般编译器会在执行了十次左右样的操作才会修改Access: 时间

    现在我们就知道,总是不被执行的原理.在我们进行make的时候,编译器会先比对它们的Modify:时间,看看源文件时间是不是大于可执行程序的时间,大于就执行,否就不执行.如果别.PHONY修饰,那么就会自动忽略这一过程.

    image-20220807141750361


    进度条

    今天我们实现一个进度条的小玩意儿,很简单,我们先看看成果

    进度条

    缓冲区

    在学习计算机时,你在很多地方有可能看到到缓冲区的字样,那什么是缓冲区啊?,我们先看一下现象

    我是在Linux环境下演示,window环境下演示可能会没有效果。

    #include     
          
      int main()    
      {    
        printf("你好,缓冲区");    
          
        sleep(3);  //在Linux环境下sleep的单位 秒                                                                                                                                                        
        return 0;    
      } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    缓冲区

    你会发现当我们执行程序时,会有大概几秒的延迟,可我们不是先执行的printf函数吗,后面才是sleep,实际上这也是事实,只不过当我执行printf后,我们想要输出的内容被放到一个缓冲区里面了,后面程序停留了3秒,当进程快要结束的时候,后面才会刷新缓冲区.下面我将仔细的说一下什么缓冲区

    本质上 缓冲区就是一块内存,我们将想要输出的数据放到缓冲区中,当我们刷新缓冲区的时候会把他打印出来,我们可以把数据当成一个水池,缓冲区看作打水的的木桶,当水桶满了的时候或者我们就是想要一半的水,就把水倒出来。

    什么情况下缓冲区会刷新

    • 全缓冲 缓冲区满了,会刷新
    • 行缓冲 我们使用换行符,缓冲区也会刷新
    • 函数缓冲 我们使用某个函数强制刷新
    • 程序退出,自动刷新

    下面我们一一演示

    全缓冲

    我们演示一下,大家就懂了

    #include 
    
    int main()
    {
        while(1)
        {
            printf("hello world");
            sleep(3);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Inked缓冲区满了

    由于时间有些长,我们就不等了,结果是当缓冲区满了后,屏幕上会出现一屏幕的 hello world

    行缓冲

    我们这里的换行是指的 \n,它可以是缓冲区刷新

    #include 
    
    int main()
    {
        while(1)
        {
            printf("hello world\n");
            sleep(3);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    换行

    函数缓冲

    在C语言中提供了一个函数,它可以强制刷新缓冲区,这就是 fflush(),它是stdio.h中的库函数,在一个C程序中,编译器会默认打开 3 个标准输入输出流,分别是下面的,这一点知道就可以了

    1. stdin
    2. stdout
    3. stderr
    #include 
    
    int main()
    {
        while(1)
        {
            printf("hello world");
            fflush(stdout);
            sleep(3);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    刷新

    \n 和 \r

    有很多人都搞不懂这两个有什么区别,我们今天重点说一下

    • \n 叫做 换行
    • \r 叫做 回车

    我们举一个写文章的例子,当我们在“我叫张三,外号法外狂徒。”这句话再开一行时,看看他们之间的区别

    image-20220305183128541

    有人可能会感到疑惑,这和我们用的不对啊,我们之前使用下面的代码时,都是再下一行的开头直接打印,实际上这是C语言的编译器默认将换行回车这两个浓缩到 \n了

    printf("hello world\n");
    printf("hello world\n");
    
    • 1
    • 2

    演示效果 \r

    我们先演示一下效果

    #include "ProcBar.h"    
          
      int main()    
      {    
        int count = 10;    
        while(count--)    
        {    
          printf("%d\r",count);    
          sleep(1);                                                                         
        }                                           
        printf("hello word\n");            
                                                    
        return 0;                                   
      } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    效果gif

    注意看,我们没有打印出9,8,7,6,5这样的数据,原因是缓冲区的因素,我们使用函数刷新一下缓冲区看看效果

    #include "ProcBar.h"    
          
      int main()    
      {    
        int count = 10;    
        while(count--)    
        {    
          printf("%d\r",count);
          fflush(stdout);
          sleep(1);                                                                         
        }                                           
        printf("hello word\n");            
                                                    
        return 0;                                   
      } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    刷新缓冲区

    进度条实现

    有了上面的知识点,我们就可以写出自己的进度条了,由于多文件情况下阅读的体验不太好,我把代码写在一个文件中

    • usleep() 和 sleep()的作用一样,不过它的单位是 毫秒

    • memset() 是 string.h库的一个函数,它可以把数组内部都初始化指定的 数据

    • %-100s 设置100的字段,- 表示 左对齐

    • %d%% 是为了了打印 百分比 %是转换说明,所以用 两个%代替一个% %% -> % 详细的请👉转换说明

    #ifndef __INCLUDE_H__  //防止头文件被重复引用
    #define __INCLUDE_H__
    
    #include 
    #include 
    
    #endif
    
    void proBar()
    {
    	const char* p = "|/-\\";
    	int i = 0;
    	char arr[101] = { 0 };
    	memset(arr, '\0', sizeof(arr));
    	while (i <= 100)
    	{
    		printf("[%-100s][%d%%][%c]\r", arr, i, p[i % 4]);
    		fflush(stdout);
    		arr[i++] = '#';
    		usleep(80000);
    	}
    	printf("\n");
    }
    
    int main()
    {
    	proBar();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这样,你的进度条就完成了,我们也可以给进度条添加颜色,C语言是支持的,不过这就要你自己去处理了

  • 相关阅读:
    涛然自得周刊(第 5 期):蝲蛄吟唱的地方
    图文详解JVM中的垃圾回收机制(GC)
    序列化方式介绍和性能比较 (kryo fastjson hessian jdk)
    机器视觉-相机选型-20220819-持续修改
    keycloak~在认证的action中自定义重定向地址
    数据结构笔记——树与二叉树
    指令跳转:原来if...else就是goto
    ShareSDK Android端主流平台分享示例
    QT信号槽
    安超云:“一云多芯”支持国家信创政务云落地
  • 原文地址:https://blog.csdn.net/m0_61334618/article/details/126223155