【Linux系统编程】5. 基础开发⼯具(下)

文章目录

一、⾃动化构建 - make/makefile

1、背景

  • 会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒

  • ⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作

  • makefile带来的好处就是⸺"⾃动化编译",⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。

  • make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可⻅,makefile 都成为了⼀种在⼯程⽅⾯的编译⽅法。

  • make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。

2、基本使⽤

myproc.c:

cpp 复制代码
#include <stdio.h>

int main()
{
	printf("hello world!\n");
 	return 0;
}

makefile:

powershell 复制代码
myproc:myproc.c
	gcc -o myproc myproc.c

.PHONY:clean
clean:
 	rm -rf myproc  

自动化编译:make

清理:make clean

makefile文件的内容解析:

项⽬清理:

  • ⼯程是需要被清理的

  • 像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动执⾏,不过,我们可以显⽰要make执⾏。即命令make clean,以此来清除所有的⽬标⽂件,以便重编译。

  • 但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤ .PHONY 修饰,伪⽬标的特性是,总是被执⾏的。

什么叫不被自动执⾏?: 当老代码(已编译过的文件)未被修改时,make 工具不会重新编译这些文件,仅对修改过的文件及其依赖进行处理。

make怎样判断代码的新旧?: make其实是通过Modify的时间来判断代码新旧的。

.PHONY的作用?: .PHONY让make忽略源⽂件和可执⾏⽬标⽂件的Modify时间对⽐。

3、推导过程

makefile:

powershell 复制代码
myproc:myproc.o
  	gcc  myproc.o -o myproc
myproc.o:myproc.s
    gcc -c myproc.s -o myproc.o
myproc.s:myproc.i
    gcc -S myproc.i -o myproc.s
myproc.i:myproc.c
    gcc -E myproc.c -o myproc.i
   
.PHONY:clean
clean:
   	rm -f *.i *.s *.o myproc     

自动化编译:make

清理:make clean

make是如何⼯作的?

  1. make会在当前⽬录下找名字叫"Makefile"或"makefile"的⽂件。

  2. 如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到 myproc 这个⽂件,并把这个⽂件作为最终的⽬标⽂件。

  3. 如果 myproc ⽂件不存在,或是 myproc 所依赖的后⾯的 myproc.o ⽂件的⽂件修改时间要⽐ myproc 这个⽂件新,那么,他就会执⾏后⾯所定义的命令来⽣成myproc 这个⽂件。

  4. 如果 myproc 所依赖的 myproc.o ⽂件不存在,那么 make 会在当前⽂件中找⽬标为myproc.o ⽂件的依赖性,如果找到则再根据那⼀个规则⽣成 myproc.o ⽂件。(这有点像⼀个堆栈的过程)

  5. 这就是整个make的依赖性,make会⼀层⼜⼀层地去找⽂件的依赖关系,直到最终编译出第⼀个⽬标⽂件。

  6. 在找寻的过程中,如果出现错误,⽐如最后被依赖的⽂件找不到,那么make就会直接退出,并报错,⽽对于所定义的命令的错误,或是编译不成功,make根本不理。

  7. make只管⽂件的依赖性,即,如果在我找了依赖关系之后,冒号后⾯的⽂件还是不在,那么对不起,我就不⼯作了。

4、语法扩展

如果有多个文件,该如何实现make?

makefile:

powershell 复制代码
BIN=proc.exe        # 定义变量
CC=gcc
SRC=$(wildcard *.c) # 获取当前目录下所有 .c 源文件
OBJ=$(SRC:.c=.o)    # 将 SRC 中的所有 .c 文件名替换为 .o
LFLAGS=-o          
FLAGS=-c          
RM=rm -f          

$(BIN):$(OBJ)                       # 链接命令:将所有 .o 文件合并为可执行文件 #
	@$(CC) $(LFLAGS) $@ $^          # $@: 代表目标文件。$^:代表依赖文件
	@echo "linking ... $^ to $@"                                                                             
%.o:%.c                             # 通配符规则:所有 .o 文件依赖于同名 .c 文件                       
	@$(CC) $(FLAGS) $<              # 编译命令:将单个 .c 文件编译为 .o 文件                
	@echo "compling ... $< to $@"   # @:不回显命令           
.PHONY:clean                                      
clean:
	$(RM) $(OBJ) $(BIN)             # 执行删除命令:删除所有 .o 文件和可执行文件
 
.PHONY:test
test:
	@echo $(SRC)                    # 打印所有 .c 源文件列表
	@echo $(OBJ)                    # 打印所有 .o 目标文件列表

自动化编译:make

清理:make clean

打印变量:make test

二、Linux第⼀个系统程序 − 进度条

1、回⻋与换⾏

  • 回车(\r):让光标回到当前行开头
  • 换⾏(\n):让光标向下移动一行

2、⾏缓冲区

我们观察三段代码:

代码一:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("hello bite!\n");
	sleep(3);

	return 0;
}

运行:输出内容,暂停3秒,程序结束。

代码二:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("hello world!");
	sleep(3);

	return 0;
}

运行:暂停3秒,程序结束才输出内容

代码三:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("hello world!");
	fflush(stdout);
	sleep(3);

	return 0;
}

运行:输出内容,暂停3秒,程序结束

结论: printf 输出的内容不会立即显示在终端,而是先存入缓冲区\n、程序结束、调用fflush(stdout) 这三种方式都会刷新缓冲区,让内容显示在终端。

3、倒计时程序

cpp 复制代码
#include<stdio.h>    
#include<unistd.h>
    
int main()    
{    
    int i=10;    
    while(i>=0)    
    {    
        printf("%-2d\r",i);    
        fflush(stdout);    
        i--;    
        sleep(1);    
    }    
    printf("\n");                                                                                                       
    
    return 0;    
} 

运行:

4、进度条代码

1)process.h

cpp 复制代码
#pragma once    
#include<stdio.h>    

void process_v1();
void FlushProcess(double total, double current);

2)process.c

cpp 复制代码
#include "process.h"
#include<string.h>
#include<unistd.h>
#define NUM 101
#define STYLE '='

void FlushProcess(double total, double current)
{
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));
    const char* lable = "|/-\\";
    int len = strlen(lable);

    static int cnt = 0;

    int num = (int)(current * 100 / total);
    int i = 0;
    for (; i < num; i++)
    {
        buffer[i] = STYLE;
    }
    double rate = current / total;
    cnt %= len;
    printf("[%-100s][%.lf%%][%c]\r", buffer, rate * 100, lable[cnt]);
    cnt++;
    fflush(stdout);
}

3)main.c

cpp 复制代码
#include"process.h"    
#include<stdio.h>    
#include<unistd.h>    

double total = 1024.0;
double speed = 1.0;

void DownLoad()
{
    double current = 0;
    while (current <= total)
    {
        FlushProcess(total, current); // 下载代码
        usleep(3000);
        current += speed; // 充当下载数据
    }
    printf("\ndownload %.2lfMB Done\n", current);
}

int main()
{
    DownLoad();

    return 0;
}

4)makefile

powershell 复制代码
SRC=$(wildcard *.c)    
OBJ=$(SRC:.c=.o)    
BIN=processbar    
    
$(BIN):$(OBJ)    
    gcc -o $@ $^    
%.o:%.c    
    gcc -c $<    
    
.PHONY:    
clean:    
    rm -rf $(OBJ) $(BIN) 

运行:

三、版本控制器 - Git

不知道你⼯作或学习时,有没有遇到这样的情况:我们在编写各种⽂档时,为了防⽌⽂档丢失,更改失误,失误后能恢复到原来的版本,不得不复制出⼀个副本,⽐如:

"报告-v1"

"报告-v2"

"报告-v3"

"报告-确定版"

"报告-最终版"

"报告-究极进化版"

...

每个版本有各⾃的内容,但最终只会有⼀份报告需要被我们使⽤。

但在此之前的⼯作都需要这些不同版本的报告,于是每次都是复制粘贴副本,产出的⽂件就越来越多,⽂件多不是问题,问题是:随着版本数量的不断增多,你还记得这些版本各⾃都是修改了什么吗?

⽂档如此,我们写的项⽬代码,也是存在这个问题的!!

1、版本控制器

为了能够更⽅便我们管理这些不同版本的⽂件,便有了版本控制器。所谓的版本控制器,就是能让你了解到⼀个⽂件的历史,以及它的发展过程的系统。通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统,同时也⽅便多⼈协同作业。

⽬前最主流的版本控制器就是 Git 。Git 可以控制电脑上所有格式的⽂件,例如 doc、excel、dwg、dgn、rvt 等等。对于我们开发⼈员来说,Git 最重要的就是可以帮助我们管理软件开发项⽬中的源代码⽂件

2、git 简史

同⽣活中的许多伟⼤事物⼀样,Git 诞⽣于⼀个极富纷争⼤举创新的年代。

Linux 内核开源项⽬有着为数众多的参与者。绝⼤多数的 Linux 内核维护⼯作都花在了提交补丁和保存归档的繁琐事务上(1991−2002年间)。到 2002 年,整个项⽬组开始启⽤⼀个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。

到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使⽤ BitKeeper 的权⼒。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使⽤ BitKeeper 时的经验教训,开发出⾃⼰的版本系统。他们对新的系统制订了若⼲⽬标:

  • 速度
  • 简单的设计
  • 对⾮线性开发模式的强⼒⽀持(允许成千上万个并⾏开发的分⽀)
  • 完全分布式
  • 有能⼒⾼效管理类似 Linux 内核⼀样的超⼤规模项⽬(速度和数据量)

⾃诞⽣于 2005 年以来,Git ⽇臻成熟完善,在⾼度易⽤的同时,仍然保留着初期设定的⽬标。它的速度⻜快,极其适合管理⼤项⽬,有着令⼈难以置信的⾮线性分⽀管理系统。

3、安装 git

Centos系统下安装git:sudo yum install git

4、在 Gitee 创建项⽬

Gitee:https://gitee.com/

注册账号

这个⽐较简单,参考着官⽹提⽰即可。

创建项⽬

  1. 登陆成功后,点击右上角的⊕,再新建仓库。

  2. 然后跳转到的新⻚⾯中输入仓库的名称,再根据自己的语言选择下面的选项。

  3. 在创建好的项⽬⻚⾯中复制项⽬的链接,以备接下来进⾏下载

下载项⽬到本地

创建好⼀个放置代码的⽬录,进入该目录,输入以下指令git clone [url]

这⾥的 url 就是刚刚建⽴好的项⽬的链接。

这样就成功从 Gitee上的用户仓库中克隆 linux 这个仓库到你的本地计算机,后面就可以从本地上传代码到Gitee上了。

5、三板斧

  1. git add :将文件提交到暂存区
powershell 复制代码
git add [文件名]
  1. git commit:将暂存区的文件提交到本地仓库
powershell 复制代码
git commit -m "XXX"

-m 后面接的XXX表示的是对于改动的内容描述。

  1. git push:将本地仓库的文件同步到gitee的远程仓库
powershell 复制代码
git push

git push时需要输入登录 gitee 时的⽤户名和密码。

如图所示:

除了三板斧之后,还有其他用法:
git log :查看当前分支的提交历史记录
git status : 查看工作区和暂存区的状态
git pull:从远程仓库拉取最新代码,并自动合并到当前本地分支

四、调试器 - gdb/cgdb

1、样例代码

mycmd.c

cpp 复制代码
// mycmd.c
#include<stdio.h>

int Sum(int s, int e)
{
    int result = 0;
    for (int i = s; i <= e; i++)
    {
        result += i;
    }
    return result;
}

int main()
{
    int start = 1;
    int end = 100;
    printf("I will begin...\n");
    int n = Sum(start, end);
    printf("running done,result is:[%d-%d]=%d\n", start, end, n);

    return 0;;
}

Makefile:

powershell 复制代码
mycmd:mycmd.c    
    gcc -o $@ $^ -std=c99 -g    
    
.PHONY:clean    
clean:    
    rm -rf mycmd    

2、预备

  • 程序的发布⽅式有两种, debug 模式和 release 模式,Linux下 gcc/g++ 默认的工作模式是 release 模式。
  • 要使⽤gdb调试,必须在源代码⽣成⼆进制程序的时候,加上 -g 选项,如果没有添加,程序⽆法被编译。

选项-g:让最后形成的可执行程序添加调试信息,也就是进入debug模式。

默认release模式: gcc mycmd.c -o mycmd

进入debug模式:gcc mycmd.c -o mycmd -g

3、常⻅使⽤

Centos系统安装gdb:sudo yum install gdb

Centos系统安装cgdb:sudo yum install cgdb

  • cgdb 是一个基于 gdb 的增强型调试工具,提供了分屏界面(源代码窗口 + 调试命令窗口),操作更直观

  • 开始: gdb/cgdb binFile

  • 退出: ctrl + dquit 命令

命令 作用 样例
list/l 显⽰源代码,从上次位置开始,每次列出10⾏ l 10
list/l 函数名 列出指定函数的源代码 l main
list/l 文件名:行号 列出指定文件中以 "行号" 为中心的 10 行代码 l mycmd.c:1
r/run 从程序开始连续执⾏ r
n/next 单步执⾏,不进⼊函数内部 n
s/step 单步执⾏,进⼊函数内部 s
break/b [⽂件名:]⾏号 在指定⾏号设置断点 b 10
break/b 函数名 在函数开头设置断点 b main
info break/b 查看当前所有断点的信息 info b
finish 执行完当前函数并暂停 finish
print/p 表达式 打印表达式的值 p start+end
p 变量 打印指定变量的值 p start
set var 变量=值 修改变量的值 set var i=10
continue/c 从当前位置开始连续执⾏程序 c
delete/d breakpoints 删除所有断点 d breakpoints
delete/d n 删除序号为n的断点 d n
disable breakpoints 禁⽤所有断点 disable breakpoints
disable n 禁⽤序号为n的断点 disable n
enable breakpoints 启⽤所有断点 enable breakpoints
enable n 启用序号为n的断点 enable n
info/i breakpoints 查看当前设置的断点列表 i b
display 变量名 自动跟踪变量值 display x
undisplay 编号 取消对指定编号的变量的跟踪显⽰ undisplay 1
until ⾏号 执⾏到指定⾏号 until 20
backtrace/bt 查看当前执⾏栈的各级函数调⽤及参数 bt
info/i locals 查看当前栈帧的局部变量值 i locals
quit 退出GDB调试器 quit

4、其他命令

1)watch

执⾏时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运⾏期间的值发⽣变化,gdb会暂停程序的执⾏,并通知使⽤者。

注意:

如果你有⼀些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变化了,就会通知你。

2)条件断点

添加条件断点

powershell 复制代码
b 8 if i == 10

给已经存在的端点新增条件

powershell 复制代码
condition 6 i == 10
相关推荐
喵叔哟8 小时前
10. 从0到上线:.NET 8 + ML.NET LTR 智能类目匹配实战--Web API 接口与前端集成:部署与生产运维:稳定性、可观测与成本
运维
杰克崔8 小时前
内核里常用宏BUG_ON/WARN_ON/WARN_ONCE
linux·运维·服务器
一枚正在学习的小白8 小时前
k8s的包管理工具helm3--流程控制语句和变量(3)
linux·运维·服务器·云原生·kubernetes
Nimsolax8 小时前
Linux网络传输层协议UDP
linux·网络·udp
维尔切8 小时前
Kafka 概述与安装部署整理
运维·分布式·kafka
dlhto8 小时前
Oracle Linux 9 的 MySQL 8.0 完整安装与远程连接配置
linux·mysql·oracle
AcrelGHP9 小时前
光储充微电网能量管理系统:构建绿色、高效、安全的能源未来
大数据·运维·人工智能
带土111 小时前
3. vim上手
linux·编辑器·vim
prettyxian11 小时前
【linux】基础开发工具(2)vim
linux·vim