【linux篇】动静态库和自动化构建的“神之一手”:make、Makefile
库:
-
动态库:动态库(Dynamic Library)是一种在程序运行时被加载的库文件
- linux:libxxx.so
- windows:xxxx.dll
-
静态库:静态库(Static Library)是指在程序编译链接时,把库文件的代码全部加入到可执行文件中,从而生成一个包含所有必需库代码的可执行文件
- linux:libxxx.a
- windows:xxxx.lib
小知识:
gcc默认形成的可执行程序是动态链接的
gcc -static形成的可执行程序是静态链接的
动静态库链接:
动态链接:(动态库又叫共享库,一旦缺失,所有依赖动态库的程序都会报错)
告诉可执行程序,动态库(共享库)的地址在哪
优点:形成可执行程序的体积小
缺点:一旦缺失,所有依赖动态库的程序都会报错
静态链接:
将库实现,在链接阶段拷贝到我们的可执行程序中
优点:一旦编译好,不依赖任何库
缺点:浪费空间(磁盘、内存空间和网络等)
make/Makefile是什么:
make是一个命令
Makefile是一个文件
Makefile是一个自动化构建的工具
make/Makefile的使用:
例子:
创建一个.c文件,在里面写入代码
在创建一个Makefile文件,里面写入如下代码。表示mycode文件依赖于code.c,下面就是正常的gcc编译
最后输入make指令运行mycode,就会生成一个叫mycode的可执行文件
然后运行就行
自动化构建核心思想:
依赖关系和依赖方法,形成目标文件
第一行表示:依赖关系,mycode的形成依赖于code.c
第二行表示:依赖方法,mycode的形成依赖于code.c,以gcc code.c -o mycode的方法形成
具体语法:
依赖关系:
mycode:目标文件
code.c:依赖文件。若依赖文件有多个,则叫依赖文件列表
依赖方法:
gcc code.c -o mycode
clean也是一个目标文件,不过它的依赖文件是空的,下面则是它的依赖方法
为什么但输入一个make只形成一个目标文件?(如果把“clean:”放到第一个,单输入一个make就是形成"clean"目标文件)
- 因为make会自顶向下扫描makefile文件默认形成第一个目标文件。如果想指定形成目标文件,就得"make+目标文件名"
makefile文件中,保存了编译器和链接器的参数选项,并且描述了所有源文件之间的关系。make程序会读取makefile文件中的数据,然后根据规则调用编译器,汇编器,链接器产生最后的输出
- Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释, B选项是正确的
-
显式规则说明了,如何生成一个或多个目标文件。
-
make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写makefile,比如源文件与目标文件之间的时间关系判断之类
-
在makefile中可以定义变量,当makefile被执行时,其中的变量都会被扩展到相应的引用位置上,通常使用 $(var) 表示引用变量
-
文件指示。包含在一个makefile中引用另一个makefile,类似C语言中的include; 根据这一项可以推导Makefile可以使用include关键字把别的Makefile包含进来。
-
注释,makefile中可以使用 # 在行首表示行注释
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件, 找到了解释这个文件
.PHONY是什么?
-
被.PHONY修饰的clean我们称为伪目标(什么是伪目标,如其名,就是假的目标)
-
目标文件被“.PHONY”修饰后,它所依赖的方法则变为:总是被执行(也就是可多次执行)
-
它所依赖的方法则变为:总是被执行(也就是可多次执行)是怎么做到的?
-
对于mycode来说,就是忽略对比时间,它的依赖方法总是被执行
当make执行一次生成mycode目标文件后,为什么不能再执行?又是怎么做到的?
-
因为mycode的依赖文件没有被修改,一模一样的文件,便不执行了。这样做是为了提高编译效率(在大型项目中,代码都是以万行打底的,运行一次项目需要几分钟几十分钟,如果不小心再次执行了一次make,那又需要生成一次目标文件,又要花上那么长时间,这是得不偿失的)
-
对比源文件与可执行文件的M时间。因为源文件的创建肯定先于可执行文件,在执行make命令时,会判断源文件的修改时间是否在可执行文件之前。若是,则允许执行make命令,否在不给执行
- 那源文件与可执行文件修改时间上是怎么定的?
-
根据文件的“ACM”时间决定
-
Access:文件的最新访问时间(不是实时更新)
-
Modify:文件内容的修改时间(一般修改文件内容,文件属性也会跟着改变,因为修改文件内容时,文件的大小会出现变化,此时文件的属性也就出现了辩护)
-
Change:文件属性的修改时间(修改属性chmod命令)
要更新一个以存在的文件的ACM时间
touch+文件名
- makefile文件注释用"#"
makefile的推导原则:
mycode文件依赖的其实是code.o文件。
- make会进行依赖关系推导,直到依赖文件是存在的
-
myproc依赖的是myproc.o文件,但myproc.o文件不存在,将依赖方法入栈
-
myproc.o依赖的是myproc.s文件,但myproc.s文件不存在,将依赖方法入栈
-
myproc.s依赖的是myproc.i文件,但myproc.i文件不存在,将依赖方法入栈
-
myproc.i依赖的是myproc.c文件,myproc.c文件存在,将依赖方法入栈
-
入栈后,出栈,执行依赖方法,生成最终的目标文件
在执行依赖方法时,生成的文件可以通过clean删除
扩展语法:
类似定义变量:
BIN赋值为mytest(目标文件)
SRC动态的获取所有源文件
OBJ赋值为依赖文件
CC赋值为gcc
RM赋值为rm -f
@echo:不让echo回显
类似变量定义:
BIN赋值为mytest(目标文件)
SRC=动态的获取所有源文件(wildcard是Makefile自身语法包含的函数)
OBJ=(SRC:.c=.o表示将所有的.c换成.o形成可执行程序。这是Makefile自己的语法)
CC赋值为gcc
RM赋值为rm -f
$^:表示将所有的OBJ文件放到(CC)后,也可以理解为:$^就是OBJ
$@:表示我们要形成的目标文件
%.o:%叫通配符,也是makefile的语法,表示匹配任意内容。允许你匹配一组文件名,而不需要显式地列出每一个文件名
%.c:表示匹配所有的.c文件
$<:表示将所有的.c文件一个一个的交给对应的命令,一个一个的形成.o
@:不让echo回显
BIN=mytest2 #SRC=$(shell ls *.c)3 SRC=$(wildcard ls *.c)#动态的获取所有源文件(wildcard是Makefile自身语法包含的函数)4 OBJ=$(SRC:.c=.o)5 CC=gcc6 RM=rm -f7 8 $(BIN):$(OBJ)9 $(CC) $^ -o $@10 @echo "链接 $^ 成 $@"11 %.o:%.c12 $(CC) -c $<13 @echo "编译 $< 成 $@" 14 15 .PHONY:clean16 clean:17 $(RM) $(OBJ) $(BIN)18 19 .PHONY:test20 test:21 @echo $(BIN)22 @echo $(SRC)23 @echo $(OBJ)
Linux第⼀个系统程序−进度条
回车与换行
回车的概念:
回车(Carriage Return,简称CR)起源于早期的打字机时代。在打字机上,有一个按键被称为回车键,按下这个按键会使打字机的滑块(或称为打印头)回到左侧边缘,同时纸张也会向上移动一行,以便继续输入文字。因此,这个按键被命名为“回车键”,因为它实现了“回车”到下一行的功能
换行的概念:光标换一行,同一列不同行
缓冲区
#include<stdio.h>
#include<unistd.h>
int main()
{//linux下一切皆文件,往显示器中打印就是往显示器中写入,加\n表示立即写入printf("hello world\n"); //没有\n,在linux下会先停两秒在输出 sleep(2);return 0;
}
休眠两秒再出来是因为程序先执行的“sleep”吗?
c语言执行代码是自上向下执行的,不可能先执行sleep。当程序执行sleep休眠时,printf肯定已经执行完了
既然printf已经执行完了,为什么在sleep期间没有看到字符串打印?
此时字符串在一个叫做“缓冲区”的地方。这个缓冲区是为显示器提供的,既然有缓冲区,那必然就会有刷新策略,而显示器的刷新策略就是:行刷新。如果我碰到的打印的字符串包含“\n”,该消息就会立即显示到显示器上
如果不想换行,就让字符串立即打印到显示器上,怎么做?
用fflush()进行强制刷新。c语言在输入输出时,默认打开了三个文件的输入输出流:stdin(键盘)、stdout、stderr(这两个都是显示器)。printf进行打印时,实际上是把消息写到了stdout里面。如果消息没刷新出来,只要用fflush()刷新一下stdout就行
进度提条
小demo
#include<stdio.h>#include<unistd.h>int main(){int cnt=10;while(cnt>=0){//如果不加"2",从10-0输出则会呈现:10、90、80、...//因为%d的意思是将输出的内容进行格式化,就是将一个整数格式化为一个字符//10格式化后为两个字符,|1|0|。除去10之外的都是一个字符,只占一位,所有会出现10、90、80...的情况printf("%-2d\r",cnt); //左对齐 fflush(stdout);cnt--;sleep(1);}// printf("hello world");// fflush(stdout);//sleep(2);return 0;}
进度条
usleep:
1秒=1000毫秒=1000微妙*1000
main.c:
#include "process.h"
#include <unistd.h>
#include <time.h>
#include <stdlib.h>//函数指针类型
typedef void (*call_t)(const char*,double,double);double total = 1024.0;
//double speed = 1.0;
double speed[] = {1.0, 0.5, 0.3, 0.02, 0.1, 0.01};//回调函数
void download(int total, call_t cb)
{srand(time(NULL));double current = 0.0;while(current <= total){cb("下载中", total, current); // 进行回调if(current>=total) break;// 下载代码int random = rand()%6;usleep(5000);current += speed[random];if(current>=total) current = total;}
}void uploadload(int total, call_t cb)
{srand(time(NULL));double current = 0.0;while(current <= total){cb("上传中", total, current); // 进行回调if(current>=total) break;// 下载代码int random = rand()%6;usleep(5000);current += speed[random];if(current>=total) current = total;}
}int main()
{download(1024.0, FlushProcess);printf("download 1024.0MB done\n");download(512.0, FlushProcess);printf("download 512.0MB done\n");download(256.0,FlushProcess);printf("download 256.0MB done\n");download(128.0,FlushProcess);printf("download 128.0MB done\n");download(64.0,FlushProcess);printf("download 64.0MB done\n");uploadload(500.0, FlushProcess);return 0;
process.c:
#include "process.h"
#include <string.h>
#include <unistd.h>#define SIZE 101
#define STYLE '='//v2: 根据进度,动态刷新一次进度条
void FlushProcess(const char *tips, double total, double current)
{const char *lable = "|/-\\";int len = strlen(lable);static int index = 0;char buffer[SIZE];memset(buffer, 0, sizeof(buffer));double rate = current*100.0/total;int num = (int)rate;int i = 0;for(; i < num; i++)buffer[i] = STYLE;printf("%s...[%-100s][%.1lf%%][%c]\r", tips, buffer, rate, lable[index++]);fflush(stdout);index %= len;if(num >= 100)printf("\n");
}// v1: 展示进度条基本功能
void process()
{int rate = 0;char buffer[SIZE];memset(buffer, 0, sizeof(buffer));const char *lable = "|/-\\";int len = strlen(lable);while(rate <= 100){printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate%len]);fflush(stdout);buffer[rate] = STYLE;rate++;usleep(10000);}printf("\n");
}