【Linux】Linux工具——make/Makefile

1.背景

  • 會不會寫makefile,從一個側面說明了一個人是否具備完成大型工程的能力
  • 一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的 規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復雜 的功能操作
  • makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編 譯,極大的提高了軟件開發的效率。
  • make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命 令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一 種在工程方面的編譯方法。
  • make是一條命令,makefile是一個文件,兩個搭配使用,完成項目自動化構建

2.make/Makefile?

2.1.見一見make/Makefile是怎么工作的

make是一個命令,Makefile是一個文件,當前目錄下的文件

可以寫makefile也可以寫Makeile

?

我們在里面寫下

第一行的意思就是mycode.c編譯成名字叫mycode的可執行程序,這個叫依賴關系

第二行是怎么編譯,這個是依賴方法

完成了以后,我們再也不用使用gcc命令來編譯了

直接生成了一個可執行程序

后面我們不想要可執行程序mycode了,我們直接打開Makefile

這樣子我們就刪掉了那個可執行程序

我們寫代碼就不用gcc了

我們系統自動存在make命令

它和Makefile一起配對使用的

我們現在了解make/Makefile是怎么使用的了

那么問題來了

2.2.什么是依賴關系?什么是依賴方法?

我們看個例子就明白了

  • 假如你是個在校大學生,快要到月底了,這時候你可能就要打電話給你爸要生活費了。你打電話給你爸爸,說 "爸,我是你兒子。",這就是表明依賴關系。你打電話告訴你爸你是他兒子的時候,實際上你的潛臺詞就是 "我要依賴你"。
  • 你給你爸打電話說:"爸我是你兒子",說完就把電話一掛,對于你爸來說會一臉懵逼 —— "這孩子今天怎么了,這是被綁架了?",你爸就不太清楚了。也就是說,你在打電話時只是表明了依賴關系,但你并沒有達到你想要做的目的(要下個月的生活費),所以正確的方法應該是:"爸,我是你兒子,我要下個月的生活費。",你表達了你是誰,并且要求給你打錢。
  • 我是你兒子 —— 表明了 "依賴關系",因為依賴關系的存在,所以才能給你打錢。
  • 打錢 —— 就是 "依賴方法",當你把依賴關系和依賴方法表明時,你就能達到要錢的目的。
  • 依賴關系不對,依賴方法再對也沒有用,比如你的舍友給你爸打電話,說:"我是你兒子的舍友,給我打錢!",你爸絕對不會打錢的。
  • 依賴方法表明了,依賴方法不正確同樣沒有用,比如你打電話給你爸:說:"我是你兒子,給我打錢我要充游戲!",你爸也不會給你打錢的!

通過上面的比喻,相信你已經知道什么是依賴關系和依賴方法了,他們必須都為真。

依賴關系和依賴方法都要為真,才能達成要錢的目的!

依賴關系:我們上面mycode的形成需要依賴于mycode.c

依賴方法:只有依賴關系可不夠,還需要指明怎么依賴

上面那個依賴關系是簡寫的,下面我們故意寫點繁瑣的?

我們把上面那個修改成下面這個更繁瑣的

我們報存,去使用它

這個好像沒有什么問題

我們看看啊,我們書寫的時候是從上往下寫的,但是它是從下面往上執行的,為什么呢?

這個是因為make在掃描makefile時是從上往下的,但是當前目錄沒有提供mycode.o,所以往下先生成mycode.o,而形成mycode.o又要形成mycode.s,依次類推,所以先生成mycode.i,剛好生成mycode.i需要mycode.c,剛好當前目錄

這個過程特別像棧

這個是makefile依賴文件的自動化推導

有人說,它既然能推導,我們要是把它變亂序還能不能推導了?

事實證明,完全沒有任何問題!!!!!

我要是故意漏掉一行會怎么樣!

結果是不能正常使用

由此,我們得到一個結論:make會自動推導Makefile中的依賴結構,棧式結構

2.3.如何清理make生成的臨時文件

我們使用make生成的東西里面有很多臨時文件

比如下面的mycode.i,mycode.o,mycode.s文件

我們需要引入一個東西來清理這些臨時文件

我們打開Makefile

clean沒有依賴關系,只執行刪除功能?

很好,全刪除了

新的問題又來了,我們看下面

我們可以使用make clean,那么是不是意味著我們可以使用make mycode ,make mycode.o,make mycode.i , make mycode .s?我們驗證一下

事實證明確實如此

我們接著看啊,我們直接把clean放到前面

我們此時再執行make指令會有什么反應?

我靠,怎么變刪除了!!!

所以make會自頂向下查找第一個依賴關系,并執行該依賴關系的依賴方法

所以我們不要把清理工作放在最前面

2.4.為什么我們只可以make一次 ,后面make多次就不行?怎么做到的?

我們看看下面這個情況

我們make使用一次之后就不讓我編譯了

為了方便,我們把Makefile全改回來簡單版本

只讓我們make一次

我們將代碼修改,再make一下,發現又可以了,再make又不行了

為什么不讓我們編譯了?

因為源文件沒有更新的話,沒有必要,提高編譯效率

那它是怎么做到的?

  1. 先有源文件,再有可執行程序——源文件一定比可執行文件先進行修改
  2. 如果我們更改了源文件,歷史上曾經還有可執行,那么源文件的最近修改時間,一定比可執行程序要新!
  3. 一般而言,源文件的最近修改時間會不會和可執行程序最近修改時間是不會一樣的,除非我們去修改了設置

基于上面兩個常識,我們就能知道為什么了

我們只需比較可執行程序的最近修改時間和源文件最近修改時間,

  1. 如果可執行程序的修改時間比源文件的修改時間新,那么說明源文件是舊的,不需要重新編譯;
  2. 如果可執行程序的修改時間比源文件的修改時間舊,那么說明源文件是新的,需要重新編譯;

我們來驗證一下上面的猜想

我們先補充一下知識

linux有一條指令stat,專門用來查看文件的生成時間

對于文件有3個時間(簡稱Acm)

  • Access(進入):最近被訪問時間
  • Modify(改變):最近文件內容被修改時間
  • Change(更改):?最近文件屬性被修改時間

?文件=文件內容+文件屬性

?在linux中,我們把文件內容改了,文件屬性也改了(大小)

我們很容易知道Access更改的頻率是非常高的,但是文件是存在在外部磁盤里,當用戶一多,更改頻率太大容易影響性能,所以Access不會每次都更改

我們去修改一下mycode.c

我們發現 全變了

?我們發現上面的Change改變了

?我們回頭去驗證

我們怎么判斷可執行程序和源文件的新舊啊?

我們一般比較Modify

其實最簡單的方法就是把它們各自的時間轉換為時間戳進行對比

mycode.c的Access更改的原因是因為gcc編譯時讀取了mycode.c

我們比較可執行程序和源文件的Modify時間,顯然可執行程序的更新

?

這里不讓我們make了

我們直接創建一個新的mycode.c

時間全更新了

這個新的mycode.c會覆蓋舊的mycode.c

這個時候啊,源文件的Modify時間比可執行程序的新,肯定可以執行make

我們看看可執行程序的Modify時間和源文件的

可執行程序的時間更新啊

?不能執行make

依次類推

我們得出結論:make會根據源文件和目標文件的新舊,判斷是否需要重新執行依賴關系!它不一定總是執行的

今天我就是想對應的依賴關系被執行呢?

那么就引入了新的語法

我們打開Makefile

改成下面這樣子

這樣子就能總是執行mycode的依賴關系啦

但是我們一般不會把這個語法用在這里

我們常常把這個語法用到清理工作,因為清理工作需要總是被執行

這樣子清理工作就能多次被執行了

2.5.特殊符號

我們在上面的Makefile文件里面寫的是

我們可以將其修改成下面這樣子

  • $@代表依賴關系的目標文件,冒號的左側
  • $^代表冒號的右側

?也能正常運轉,我們發現使用make就會回顯,我們不想回顯,怎么做呢?

這樣子即可

3.Linux第一個項目——進度條

制作進度條,我們需要一些儲備知識

3.1.回車換行

我們對回車換行可能有的誤解

真正的回車換行應該是下面這樣子的

  • 回車(Carriage Return):在打字機時代,回車指的是將打字機的打印頭(稱為"carrier")移回到行首的操作。在計算機時代,回車通常表示將光標移動到當前行的開頭,而不會換到下一行。在ASCII字符集中,回車通常用"\r"表示。
  • 換行(Line Feed)::換行是指將光標移動到下一行的操作,使得文本在縱向上向下移動一個行高。在ASCII字符集中,換行通常用"\n"表示。

  • 在Unix和類Unix系統(如Linux和macOS)中:通常使用換行字符(“\n”)來表示換行。
  • 在Windows系統中:,通常使用回車和換行的組合來表示換行,即"\r\n"。

?3.2.緩沖區

緩沖區(Buffer)是計算機內存中的一塊特定區域,用于臨時存儲數據。它在許多計算機系統和應用程序中發揮著重要作用,通常用于臨時存儲輸入數據、輸出數據或在內存和其他設備之間進行數據傳輸。


輸入緩沖區:用于暫時存儲從輸入設備(如鍵盤、鼠標、網絡接口等)接收到的數據,直到程序能夠處理它們。輸入緩沖區使得程序可以按需處理輸入,而不必擔心輸入數據的速度與程序處理速度不匹配的問題。

輸出緩沖區:用于暫時存儲將要發送到輸出設備(如顯示器、打印機、網絡接口等)的數據,直到設備準備好接收它們。輸出緩沖區可以提高數據傳輸的效率,因為程序不必等待設備就緒就可以繼續執行

3.2.1.緩沖區何時被清理

拿C語言舉個例子:

在C語言中,標準庫函數printf()用于將格式化的數據打印到標準輸出流(通常是終端)。但是,printf()函數并不會立即將數據顯示到終端上。相反,它會將數據寫入到輸出緩沖區中。輸出緩沖區是一個臨時存儲區域,用于存放printf()函數打印的數據,直到滿足一定條件時才將其刷新(即將數據發送到終端并顯示出來)。

這些條件包括:

  1. 遇到換行符 \n:當printf()函數遇到換行符時,輸出緩沖區會被自動刷新,將緩沖區中的數據輸出到終端并顯示出來。
  2. 緩沖區滿:當輸出緩沖區滿了,它也會被自動刷新。
  3. 調用fflush()函數:顯式調用fflush(stdout)函數可以強制刷新輸出緩沖區,將其中的數據輸出到終端。
  4. 程序結束:當程序正常終止時,所有的緩沖區都會被刷新。

3.2.2.驗證緩沖區存在

我們先寫出基本的代碼結構

processBar.h

processBar.c我們暫時不寫

main.c

我們新建一個makefile,寫成下面這個

我們為什么只寫兩個源文件,不寫頭文件呢?

這個是因為頭文件在makefile的同一目錄里,我們的main.c包含了這個頭文件,make會自動尋找頭文件

沒有任何問題?

我們基于這個結構來驗證一下緩沖區的問題

我們可以查找sleep函數

我們編譯運行上面那個代碼

發現hello world直接出來了,沒有等待3ms?

我們更新一下代碼

helloworld居然沒有先出來出來

在等待的這段時間里,helloworld是在輸出緩沖區里

后出來的helloworld

我們再更新 一下代碼

fflush函數可以去man3號手冊查

直接輸出了

3.3.代碼?

有了以上的知識儲備,咱們就可以嘗試編寫一下簡單的倒計時程序了,思路如下:

  • 首先新建一個time.c文件,然后再用我們之前講的makefile工具來實現time.c文件的自動構建:

#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;
}

make命令進行編譯:?

  • 這里有個小拓展,如果我們要覆蓋上次的數字是4位,這次是三次(比如1000到999),可以用%4d這個輸出形式來解決,也可以用下面這種方法:
#include <stdio.h>
#include <unistd.h>
int main()
{int cnt = 1000;int tmp = cnt;int num = 0;while (tmp){++num;tmp /= 10;}while(cnt >= 0){// 主要就是這里的變化,用最大數字的位數來做占位符printf("倒計時:%*d\r",num, cnt);fflush(stdout);--cnt;sleep(1);}printf("\n");return 0;
}

總共有三個部分:
?

1. 我們要實現的進度條用#來進行加載;

2. 后面要有數據來表示現在加載的進度是多少(百分數);

3. 最后用一個動態旋轉的類?來表示程序還在繼續加載

1. 動態加載的過程

動態加和之前的倒計時差不多,每次都要覆蓋上次出現的#,具體思路如下:


1. 定義一個字符類型數組char *str,用memset()函數進行初始化(‘\0’);

2. 循環100次,每次循環都在數組中加一個#,并打印str('\r’進行覆蓋);

3. 強制沖刷緩沖區;

2. 進度加載

我們可以用每次循環的次數來當作是當前加載的進度,當然還要進行覆蓋,具體思路如下:


1. 每次循環都以當前的循環次數作為加載進度;

2. 每次覆蓋上一次的進度;

3. 強制沖刷緩沖區。

4. 程序休眠(可以用usleep()函數,單位是微秒)

3. 動態旋轉

定義一個數組,并初始化為-\\/-,覆蓋的方法和之前類似,就不詳細說了。

#include "process_bar.h"
#include <memory.h>
#include <unistd.h>
#define style '#'
#define round "-\\/-"
void test()
{int i = 0;char str[100];memset(str,'\0',sizeof(str));while (i <= 100){str[i] = style;printf("[%-100s][%d%%][%c]\r",str,i,round[i % 4]);fflush(stdout);++i;usleep(10000);}printf("\n");
}

?3.4.第二版本

我們正常用進度條肯定不是單獨使用的,會結合其他的場景,例如下載界面,登陸界面

對于要下載的文件,肯定有文件大小,下載的時候網絡也有它的帶寬,所以在下載的時候,每次下載的大小都是一個帶寬,我們可以先寫一個下載的函數:

void download()
{double bandwidth = 1024 * 1024 * 1.0;double filesize = 1024 * 1024 * 10.0;double cur = 0.0;while (cur <= filesize){// 調用進度條函數test(filesize, cur);// 每次增加帶寬cur += bandwidth;usleep(20000);}printf("\n");printf("this file has been downloaded\n");
}
void test(double total, double current)
{char str[101];memset(str,'\0',sizeof(str));int i = 0;// 這次的比率double rate = (current * 100) / total;// 循環次數int loop_count = (int)rate;while (i <= loop_count){str[i++] = style; }printf("[%-100s][%.1lf%%][%c]\r",str,rate,round[loop_count % 4]);fflush(stdout);
}
// 頭文件 process_bar.h
#include <stdio.h>typedef void(*callback_t)(double, double);// 函數指針(回調函數)void test(double total, double current);// 函數實現文件 process_bar.c
#include "process_bar.h"
#include <memory.h>
#include <unistd.h>
#define style '#'
#define round "-\\/-"void test(double total, double current)
{char str[101];memset(str,'\0',sizeof(str));int i = 0;double rate = (current * 100) / total;int loop_count = (int)rate;while (i <= loop_count){str[i++] = style; }printf("[%-100s][%.1lf%%][%c]\r",str,rate,round[loop_count % 4]);fflush(stdout);
}// main.c 主函數和 download 函數
#include "process_bar.h"
#include <unistd.h>double bandwidth = 1024 * 1024 * 1.0;
void download(double filesize, callback_t cb)
{double cur = 0.0;while (cur <= filesize){cb(filesize, cur);cur += bandwidth;usleep(20000);}printf("\n");printf("this file has been downloaded\n");
}int main()
{download(1024*1024*100.0,test);download(1024*1024*20.0,test);return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23735.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23735.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23735.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Edge 工作區是什么?它都有哪些作用?

什么是工作區 Edge 工作區是什么&#xff1f;它是微軟 Edge 瀏覽器中的一個功能&#xff0c;在幫助用戶更好地組織和管理他們的瀏覽會話。通過工作區&#xff0c;用戶可以創建多個獨立的瀏覽環境&#xff0c;每個工作區內包含一組相關的標簽頁和瀏覽器設置。這使得用戶能夠根據…

SQL進階day9————聚合與分組

目錄 1聚合函數 1.1SQL類別高難度試卷得分的截斷平均值 1.2統計作答次數 1.3 得分不小于平均分的最低分 2 分組查詢 2.1平均活躍天數和月活人數 2.2 月總刷題數和日均刷題數 2.3未完成試卷數大于1的有效用戶 1聚合函數 1.1SQL類別高難度試卷得分的截斷平均值 我的錯誤…

開放式耳機十大品牌推薦!怎么選耳機看這六招!

隨著耳機廠家的瘋狂內卷&#xff0c;以前讓學生黨望其項背的千元耳機技術&#xff0c;紛紛被廠家下沉至百元耳機&#xff0c;是以2024年始&#xff0c;百元開放式耳機以新物種、價低格而爆火。看到身邊朋友爭相購買開放式耳機&#xff0c;既當耳飾&#xff0c;又當耳機&#xf…

分享:2024年(第12屆)“泰迪杯”數據挖掘挑戰賽成績公示

2024年&#xff08;第12屆&#xff09;“泰迪杯”數據挖掘挑戰賽歷時兩個月順利結束。競賽采用盲審&#xff08;屏蔽參賽者信息&#xff1b;評審專家只能評閱非本區域作品&#xff1b;三位評閱專家同時評閱同一作品&#xff0c;超限調整后再取平均分&#xff09;&#xff0c;答…

redis做為緩存,mysql的數據如何與redis進行同步呢?

讓我們一步步來實現如何讓MySQL數據庫的數據和Redis緩存保持同步。想象一下&#xff0c;MySQL是一個大倉庫&#xff0c;存放著所有重要的貨物&#xff08;數據&#xff09;&#xff0c;而Redis則像是一個快速取貨窗口&#xff0c;讓你能更快拿到常用的東西。為了讓兩者保持一致…

FC-135是一款受歡迎的32.768kHz晶振

KHZ中愛普生是以32.768KHZ最為出名的。32.768K晶振是一款數字電路板都要使用到的重要部件&#xff0c;有人比喻為電路板的冰發生器&#xff0c;也就是說心如果停止了跳動&#xff0c;那么電路板也將無法進行穩定的工作了&#xff0c;愛普生晶振FC-135是一款受歡迎的32.768KHz晶…

ALOS PALSAR 產品介紹

簡介 L1.0 產品 該產品由原始觀測數據&#xff08;0 級&#xff09;通過數據編輯&#xff08;如位重新調整和添加軌道信息&#xff09;生成。它是重建的、未經處理的信號數據&#xff0c;帶有輻射和幾何校正系數&#xff08;附加但未應用&#xff09;。 L1.1 產品 該產品由…

Java Web學習筆記13——JSON

JavaScript自定義對象 定義格式&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Js-對…

前端面試題日常練-day57 【面試題】

題目 希望這些選擇題能夠幫助您進行前端面試的準備&#xff0c;答案在文末 1. 在PHP中&#xff0c;以下哪個符號用于連接兩個字符串&#xff1f; a) b) . c) , d) : 2. PHP中的預定義變量$_SESSION用于存儲什么類型的數據&#xff1f; a) 用戶的輸入數據 b) 瀏覽器發送的…

前后端不分離與前后端分離的Java Web開發對比介紹

在現代Web開發中&#xff0c;前后端架構設計有兩種主要模式&#xff1a;前后端不分離和前后端分離。本文將詳細介紹這兩種模式&#xff0c;展示如何使用Spring Boot開發應用&#xff0c;并提供可運行的示例代碼。 前后端不分離的Java Web開發 在前后端不分離的架構中&#xf…

Linux 命令 `uniq`:去重利器

Linux 命令 uniq&#xff1a;去重利器 在 Linux 系統中&#xff0c;處理文本數據是日常任務中不可或缺的一部分。當我們面對大量重復的數據行時&#xff0c;如何高效地去除這些重復項成為了一個值得探討的話題。這時&#xff0c;uniq 命令就派上了用場。本文將介紹 uniq 命令的…

走進AI大模型的瘦身房,看看如何把大模型塞進我們的手機里

人工智能的廣泛應用已經改變了我們的生活方式。從智能助手到自動駕駛汽車&#xff0c;AI技術正變得越來越普及。然而&#xff0c;這些AI大模型往往參數眾多&#xff0c;體積龐大&#xff0c;需要依賴龐大的網絡計算資源&#xff0c;如何讓大模型能在個人電腦&#xff0c;甚至手…

【docker】 /bin/sh: ./mvnw: No such file or directory解決方案.dockerignore被忽略

報錯如下&#xff1a;解決方案很簡單&#xff0c;但是容易讓大家忽視的問題。 > CACHED [stage-1 2/4] WORKDIR /work/ …

【Android面試八股文】使用equals和==進行比較的區別?

使用equals和==進行比較的區別 這道題想考察什么 ? 在開發中當需要對引用類型和基本數據類型比較時應該怎么做,為什么有區別。 考察的知識點 equals 的實現以及棧和堆的內存管理 考生應該如何回答 在 Java 中,equals() 方法和 == 運算符用于比較對象之間的相等性,但它…

數據賦能(111)——體系:監控數據采集——概述、關注焦點

概述 監控數據采集是指對數據采集過程進行實時的監視和控制&#xff0c;以確保數據的準確性、完整性和可用性。監控數據采集旨在及時發現并解決數據采集過程中出現的問題&#xff0c;保證數據的穩定性和可靠性。 監控數據采集的主要目的是確保數據的準確性、完整性和可用性。…

要改進單例模式的實現以確保線程安全并優化性能,有幾種常見的方法

要改進單例模式的實現以確保線程安全并優化性能&#xff0c;有幾種常見的方法。以下是幾種改進 ThreadUtil 單例實現的方法&#xff1a; ### 1. 懶漢式&#xff08;線程安全版&#xff09; 使用同步機制來確保線程安全&#xff0c;但只在第一次創建實例時同步&#xff0c;這樣…

正則匹配規則

正則表達式&#xff1a;查找某字符串開始和某字符串結束的字符串 a.*?b 查找以a開始&#xff0c;并且以b結束的字符串 例如&#xff1a; 字符串為&#xff1a;上海12345abcd.opi,.<>北京 patten &#xff1a;上海.*?北京 結果&#xff1a;上海12345abcd.opi,.<>…

用互斥鎖解決緩存擊穿

我先說一下正常的業務流程&#xff1a;需要查詢店鋪數據&#xff0c;我們會先從redis中查詢&#xff0c;判斷是否能命中&#xff0c;若命中說明redis中有需要的數據就直接返回&#xff1b;沒有命中就需要去mysql數據庫查詢&#xff0c;在數據庫中查到了就返回數據并把該數據存入…

Unity DOTS技術(三)JobSystem+Burst+批處理

文章目錄 一.傳統方式二.使用JobSystemBurst方式三.批處理 在之前的例子中我們都中用的單線程與傳統的編譯器,下面我們試著使用JobSystem與打找Burst編譯器來對比一下性能的差異. 一.傳統方式 1.首先用傳統方式創建10000個方塊并讓基每幀旋轉 2.我們可以看到他的幀率是40 …

RBAC 模型梳理

1. RBAC 模型是什么 RBAC&#xff08;Role-Based Access Control&#xff09;即&#xff1a;基于角色的權限控制。通過角色關聯用戶&#xff0c;角色關聯權限的方式間接賦予用戶權限。 RBAC 模型由 4 個基礎模型組成&#xff1a; 基本模型 RBAC0&#xff08;Core RBAC&#x…