Linux系统与系统编程(5)——动静态库、自动化构建make/Makefile、回车与换行相关的行缓冲区
前言
欢迎观看Linux系列文章!!第5篇主要讲述了动静态库、自动化构建make/Makefile、回车与换行相关的行缓冲区。
目录
前言
动静态库
动态链接
静态链接
自动化构建-make/Makefile
如何使用
语法
make的执行
注释使用 #
依赖关系推导
定义变量&使用变量
@拒绝回显
$^ 和 $@
%通配符与$<
多行依赖方法
完整演示代码
回车与换行
行缓冲区
缓冲区刷新
强制刷新
与行缓冲相关的小程序
简单倒计时程序
简单进度条程序
动静态库
Linux中,动态库文件命名格式一般为 libxxxx.so,静态库文件命名格式一般为 libxxxx.a;
Windows中,动态库文件命名格式一般为 xxxx.dll,静态库文件命名格式一般为 xxxx.lib。
ldd code可以看到可执行文件依赖于什么库。
链接的库文件实际上就是C标准库
使用file命令,可以查看可执行文件的基本信息
file code图中可见,code可执行文件是64bit,executable可执行文件,dynamically linked动态链接形成的。
说明code可执行文件是由动态链接而来。
动态链接
动态库是共享的,一旦丢失,所有依赖这个动态库的程序都会运行出错。将动态库里的库方法地址拷到可执行文件中,通过地址来调用库方法。
静态链接
将静态库里的库方法直接拷贝到可执行程序里,会让可执行文件的大小变得很大。
优点:不依赖库
缺点:浪费资源(磁盘空间,内存,网络等)。
自动化构建-make/Makefile
make是一个命令,makefile是一个文件
如何使用
①在同级目录下,创建Makefile文件(也可以makefile,首字母不大写)。
②为Makefile添加内容,并保存退出
第一行表示的是依赖关系,
第二行表示的是依赖方法。
两者必须同时存在
③使用make命令,就会执行Makefile中的内容
从而实现快速编译。
语法
make的执行
直接执行make命令,默认形成第一个文件。如果想指定形成就带上目标文件名称
make make clean用make编译完成后,若源代码文件没有被修改,那么它不会再进行编译,这是为了提高效率,已完成的事情,没必要再做一次。
这是通过对比源文件和可执行文件的修改时间来对比的。
我们也可以有办法查看文件的修改时间
stat test.c关于访问时间:因为访问文件一般都很频繁,所以为了节省资源提高效率,访问时间不会每次访问都改,而是访问达到某一数量再改。
而之前说过的touch命令,可以使文件的A、M、C三个时间都更新一遍。
若用.PHONY修饰 mycode,将其变为伪目标,就可以多次编译了。
成为伪目标的依赖方法,不管执行几次,都会再执行。
注释使用 #
依赖关系推导
如图修改Makefile
make会根据Makefile给到的依赖关系,自动推导执行依赖方法
由于依赖关系①的依赖文件需要②生成,②的依赖文件需要③生成,③的依赖文件需要④生成,而④的依赖文件code.c刚好就有。于是make指令就按④③②①的顺序推导执行。
底层用的是栈的结构来完成推导的,无法完成的依赖方法就依次入栈,直到遇到可以执行的依赖方法,就依次出栈。
定义变量&使用变量
这里我们自定义设置了变量BIN、SRC、OBJ、RM、CC,通过$([变量名]),替换下文的依赖关系和依赖方法。
当我们使用make的时候,就会自动把变量替换到指令中。以后,我们只需要修改变量,就可以适配不同的文件,从而便于不同文件的编译了。
但是这样依旧还是不够通用。
还可以这样
行23相当于下面的代码。
SRC=$(wilcard *.c)使用shell,在make的时候,会先调用shell的命令 (这里是ls *.c),然后把命令会把打印在终端的内容给到SRC,这样就可以做到批量操作一堆文件了。
当然OBJ也要改,这里把SRC的所有内容中的 .c 换为 .o 。这样一来,OBJ也能表示对应SRC的一批 .o 文件了。
@拒绝回显
依赖方法前加一个@,make的时候,就不会回显了。
不加@
加@
$^ 和 $@
我们可以用$^省略表示目标文件,$@省略表示依赖关系列表。
同下
%通配符与$<
表示匹配内容
此处表示含有 .c 的文件都作为依赖文件,并把目标文件依照 .c 依赖文件列表依次生成对应目标文件,并把匹配的 .c 改为 .o。
通配符要搭配 $< 使用,意思是把批量的匹配到的依赖文件,一个一个放到该位置并生成对应的目标文件。意思是,把一堆 .c 文件依次 -c 选项编译后,生成一堆目标文件,并把依赖文件的 .c 换为 .o 作为目标文件的命名。
多行依赖方法
依赖方法可以支持多行。
由此,可以实现自定义的回显方式。
完整演示代码
BIN=mycode #SRC=$(wildcard *.c) SRC=$(shell ls *.c) OBJ=&(SRC:.c=.o) RM=rm -f CC=gcc $(BIN):$(OBJ) @$(CC) $^^ -o $@ @echo "编译 $^ 成 $@" %.o:%.c $(CC) -c $< @echo "编译 $^ 成 $@" .PHONY:clean clean: @$(RM) $(BIN) $(OBJ)回车与换行
换行就是换到下一行,而回车是先把光标放回当前行的开头,再换行。两者是不一样的。
行缓冲区
缓冲区刷新
原code.c
#include<stdio.h> #include<unistd.h> #define M 5 int main() { printf("hello world\n"); printf("hello M=%d\n", M); sleep(2); return 0; }编译运行后,打印的内容会立即显示到终端,并等待2秒后结束。
修改code.c,删去换行符。
#include<stdio.h> #include<unistd.h> #define M 5 int main() { printf("hello world"); printf("hello M=%d", M); sleep(2); return 0; }编译运行后,等待了2秒,打印的内容才会显示到终端上。
毫无疑问,C语言程序肯定是先执行printf再执行sleep的,但是为什么最终效果看上去是先sleep再printf呢?
就是因为缓冲区的存在!
printf的内容是要打印到显示器终端的,所以要打印的字符会先放到缓冲区的内存中。缓冲区有对应的刷新策略。对于显示器,刷新策略是行刷新,就是每次换行进行依次刷新,每次刷新就把缓冲区的所有内容打印到显示器上。最后在程序退出时,也会刷新缓冲区,释放缓冲区内存。
原code.c打印的内容自带 \n ,所以打印的内容进了缓冲区之后立马就刷新打印到显示器了;
修改后的code.c打印的内容不带 \n ,打印的内容进入了缓冲区之后没有被刷新,只有等到程序结束了,才会把缓冲区刷新,输出所有内容。
强制刷新
如果想要强制刷新缓冲区,可以使用fflush函数。
#include<stdio.h> #include<unistd.h> #define M 5 int main() { printf("hello world"); printf("hello M=%d", M); fflush(stdout); sleep(2); return 0; }fflush有三个标准源参数:stdin,stdout,stderr。stdin是键盘输入缓冲,stdout和stderr都是显示器输出,一个输出的是程序正常执行时输出的内容,一个输出的是报错时的错误信息。我们可以用fflush强制刷新stdout标准源,达到刷新缓冲区的目的。
注意:在Linux下不要 fflush(std) ,不然会报错;stderr没有缓冲机制,所以其实强制刷新对它无效。
与行缓冲相关的小程序
简单倒计时程序
#include<stdio.h> #include<unistd.h> int main() { int cnt = 10; while(cnt >= 0) { printf("%-2d\r", cnt); fflush(stdout); cnt--; sleep(1); } printf("\n"); return 0; }① \r 让光标回车不换行,即回到行首,在下次输出时可以覆盖原内容。
②因为没有是要 \n 换行,所以要用fflush强制刷新才可以及时输出倒计数数字。
③要格式化为%-2d,即左对齐两个字符,不足两个字符空格补齐。打印完10之后,光标回退到开头,继续打印个位数会有空格覆盖10的0字符。
简单进度条程序
process.h
#pragma once #include<stdio.h> void process(); void Flushprocess(double total, double current);process.c
#include"process.h" #include<string.h> #include<unistd.h> #define SIZE 101 #define STYLE '#' void process() { int rate = 0; char buffer[SIZE]; char load[4]={'/', '|', '\\', '-'}; memset(buffer, ' ', sizeof(buffer)); buffer[100] = '\0'; while(rate <= 100) { printf("[%s][%d\%][%c]\r", buffer, rate, load[rate % 4]); fflush(stdout); buffer[rate] = STYLE; rate++; usleep(50000); } printf("\n"); } void Flushprocess(double total, double current) { char buffer[SIZE]; memset(buffer, ' ', sizeof(buffer)); buffer[SIZE - 1]= '\0'; int num = (int)(current * 100 / total); double rate = current * 100.0 / total; char load[4]={'/', '|', '\\', '-'}; static int index = 0; for(int i = 0; i < num; i++) { buffer[i] = STYLE; } printf("[%-100s][%.2lf%%][%c]\r", buffer, rate, load[index % 4]); if(num == 100) { printf("[%-100s][%.2lf%%][*]\r", buffer, rate); } index++; }main.c
#include"process.h" #include<unistd.h> #include<time.h> #include<stdlib.h> //double total = 1024.0; //double speed = 1.0; //函数指针类型 typedef void (*call_t)(double, double); void download(double total, call_t cb)//回调函数 { srand(time(NULL)); double current = 0.0; double speed = rand() % 3 + 1; while(current <= total) { cb(total, current); if(current >= total) break; usleep(1000); current += speed; if(current >= total) current = total; } } int main() { //process(); download(1024.0, Flushprocess); printf("\n"); download(3472.0, Flushprocess); printf("\n"); download(23626.0, Flushprocess); printf("\n"); download(52.0, Flushprocess); printf("\n"); download(52672.0, Flushprocess); printf("\n"); return 0; }process和Flushprocess是两个版本的进度条程序,前者是简单版本的。
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤
