久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2107|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

C項目的文件組織和GCC編譯步驟分解

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:912806 發(fā)表于 2021-8-26 11:41 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
本帖最后由 michaelchain 于 2021-8-26 11:45 編輯

C項目的文件組織和編譯
C項目的代碼, 由頭文件(.h后綴)和C文件(.c后綴)組成
  • C語言的函數(shù)和變量, 分聲明和定義兩個階段
  • 頭文件和C文件是等價的, 相當(dāng)于C文件的一部分, 其功能由人為劃分, 用于變量和函數(shù)的聲明, 頭文件也可以用于變量和函數(shù)的定義, 但是這屬于非標(biāo)準(zhǔn)用法, 一般不這么用
  • 同一個編譯中, 函數(shù)在一處定義, 處處可用(除非使用static關(guān)鍵字)
    • 在A.c中定義后, 在B.c中用extern聲明這個函數(shù), 就可以調(diào)用
    • 將A.c中的函數(shù)聲明提取到A.h, 在B.c中include A.h, 或者通過B.c include B.h, B.h include A.h, 都可以實現(xiàn)函數(shù)引用
  • C的編譯, 是按文件編譯的, 每個C文件會編譯為一個目標(biāo)文件
  • 頭文件不單獨編譯, 與include這個頭文件的C文件, 在預(yù)編譯階段展開, 之后在C文件中編譯
  • 編譯需要知道C文件的列表和頭文件的目錄列表
  • 編譯會依次編譯C文件列表中的每個文件, 不管最終是否用到
C項目結(jié)構(gòu)示例
定義一個頭文件 inc.h,聲明兩個函數(shù)func1和func2, 將定義寫在func1.c和func2.c. 在main.c中通過main.h引用inc.h, 調(diào)用這些函數(shù), 程序目錄結(jié)構(gòu)如下
  1. ├── inc
  2. │   ├── func1.c
  3. │   ├── func2.c
  4. │   └── inc.h
  5. ├── main.c
  6. ├── main.h
  7. └── obj
復(fù)制代碼

main.c
  1. #include <stdio.h>
  2. #include "main.h"

  3. int main()
  4. {
  5.   uint8_t a = 0x08;
  6.   uint8_t b = func1(a);
  7.   printf("%X", b);
  8.   return 0;
  9. }
復(fù)制代碼

main.h
  1. #ifndef MAIN_H
  2. #define MAIN_H

  3. #include "inc.h"

  4. #endif
復(fù)制代碼

inc.h
  1. #ifndef INC_H
  2. #define INC_H

  3. typedef unsigned char uint8_t;

  4. uint8_t func1(uint8_t a);
  5. uint8_t func2(uint8_t a);

  6. #endif
復(fù)制代碼

func1.c
  1. #include "inc.h"

  2. uint8_t func1(uint8_t a)
  3. {
  4.   a = a << 1;
  5.   return a;
  6. }
復(fù)制代碼

func2.c
  1. #include "inc.h"

  2. uint8_t func2(uint8_t a)
  3. {
  4.   a = a >> 1;
  5.   return a;
  6. }
復(fù)制代碼

gcc的編譯過程
gcc命令其實依次執(zhí)行了四步操作
  • 預(yù)處理(Preprocessing),
  • 編譯(Compilation),
  • 匯編(Assemble),
  • 鏈接(Linking)


1.預(yù)處理(Preprocessing)
預(yù)處理用于將所有的#include頭文件以及宏定義替換成其真正的內(nèi)容,預(yù)處理之后得到的仍然是文本文件,但文件體積會大很多。gcc的預(yù)處理是預(yù)處理器cpp來完成的,你可以通過如下命令對 main.c進(jìn)行預(yù)處理:
gcc -E -I./inc main.c -o obj/main.i# or$ cpp main.c -I./inc -o obj/main.i
-E是讓編譯器在預(yù)處理之后就退出,不進(jìn)行后續(xù)編譯過程; -I指定頭文件目錄, -o指定輸出文件名.
經(jīng)過預(yù)處理之后代碼體積會大很多, main.c只有10行, 但是main.i有749行, 預(yù)處理之后的文件可以用文本編輯器查看

2.編譯(Compilation)
這一步的編譯將經(jīng)過預(yù)處理之后的程序轉(zhuǎn)換成特定匯編代碼的過程, 編譯的命令如下:
$ gcc -S -I./inc main.c -o obj/main.s
-S讓編譯器在編譯之后停止. 這一步會生成程序的匯編代碼, 內(nèi)容如下:
        .file        "main.c"
        .text
        .section        .rodata
.LC0:
        .string        "%X"
        .text
        .globl        main
        .type        main, @function
main:
.LFB0:
        .cfi_startproc
        endbr64
        pushq        %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq        %rsp, %rbp
        .cfi_def_cfa_register 6
        subq        $16, %rsp
        movb        $8, -2(%rbp)
        movzbl        -2(%rbp), %eax
        movl        %eax, %edi
        call        func1@PLT
        movb        %al, -1(%rbp)
        movzbl        -1(%rbp), %eax
        movl        %eax, %esi
        leaq        .LC0(%rip), %rdi
        movl        $0, %eax
        call        printf@PLT
        movl        $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size        main, .-main
        .ident        "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
        .section        .note.GNU-stack,"",@progbits
        .section        .note.gnu.property,"a"
        .align 8
        .long         1f - 0f
        .long         4f - 1f
        .long         5
0:
        .string         "GNU"
1:
        .align 8
        .long         0xc0000002
        .long         3f - 2f
2:
        .long         0x3
3:
        .align 8
4:


3.匯編(Assemble)
匯編過程將上一步的匯編代碼轉(zhuǎn)換成機器碼(machine code),這一步產(chǎn)生了二進(jìn)制的目標(biāo)文件, gcc匯編過程通過as命令完成
as obj/main.s -o obj/main.o# porgcc -c obj/main.s -o obj/main.o
這一步需要給每一個源文件產(chǎn)生一個目標(biāo)文件, 以便后面link
gcc -c -I./inc inc/func1.c -o obj/func1.ogcc -c -I./inc inc/func2.c -o obj/func2.o

4.鏈接(Linking)
通過上面的步驟, 在obj目錄下已經(jīng)有main.o, func1.o和func2.o這三個目標(biāo)文件, 現(xiàn)在需要通過linker將這些目標(biāo)文以及所需的庫文件(.so等)鏈接成最終的可執(zhí)行文件(executable file)
命令如下
gcc -o obj/main obj/main.o obj/func1.o obj/func2.o
這時候在obj目錄下就會生成可執(zhí)行文件main

鏈接并不會忽略未使用的目標(biāo)文件
上面的編譯產(chǎn)生的main文件大小為16824字節(jié), 不管在main中是否調(diào)用了func1或者func2.
如果在link中去掉func2.o (因為main中未調(diào)用func2, 所以不會產(chǎn)生錯誤), 這樣產(chǎn)生的main文件為16760字節(jié)
gcc -o obj/main obj/main.o obj/func1.o
如果需要減小尺寸, 可以使用 -fdata-sections -ffunction-sections -Wl --gc-sections -Os等參數(shù)優(yōu)化. 例如
gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

頭文件, 靜態(tài)庫(.lib, .a) 和動態(tài)庫(.dll, .so)靜態(tài)庫 vs 動態(tài)庫
庫文件就是已經(jīng)預(yù)編譯好的目標(biāo)文件, 只需要link到你的程序里就可以用了, 例如常見的方法 printf() and sqrt(). 庫文件有兩種類型: 靜態(tài)庫和動態(tài)庫(也叫共享庫).
靜態(tài)庫 在Linux下使用擴(kuò)展名.a, 在Windows下使用擴(kuò)展名.lib, 當(dāng)link靜態(tài)庫時, 這些對象文件的機器碼會被復(fù)制到你的可執(zhí)行文件中.
動態(tài)庫 在Linux下使用擴(kuò)展每.so, 在Windows下使用擴(kuò)展名.dll, 當(dāng)你的程序link靜態(tài)庫時, 只會在你的程序可執(zhí)行文件中添加一個表, 在運行你的程序之前, 操作系統(tǒng)會將這些外部方法的機器碼載入進(jìn)來. 這種方式可以節(jié)約磁盤資源, 讓程序更小, 另外大多數(shù)操作系統(tǒng)也運行內(nèi)存中的一份動態(tài)庫在多個運行的程序中共享. 動態(tài)庫升級時無需重新編譯執(zhí)行程序.
GCC默認(rèn)情況下以動態(tài)庫方式link. 要查看庫內(nèi)容, 可以用命令nm filename

編譯中定位包含頭文件和庫文件 (-I, -L and -l)
當(dāng)編譯項目時, 編譯器需要頭文件的信息, linker需要庫文件解決外部依賴.
對于項目中include的頭文件, 編譯器會去搜索相應(yīng)的路徑, 這些路徑通過 -Idir 參數(shù) ( 或者環(huán)境變量 CPATH) 指定, 因為頭文件的文件名是已知的, 所以編譯器只需要知道路徑.
對于linker, 會去搜索庫路徑, 這個通過 -Ldir 參數(shù) (大寫 'L' 后面是路徑) (或者環(huán)境變量 LIBRARY_PATH). 另外你需要指定庫名稱. 在Unix系統(tǒng)中, 庫文件 libxxx.a 通過參數(shù) -lxxx 指定 (小寫字符 'l' 不帶lib前綴, 不帶.a擴(kuò)展名). 在Windows下, 需要提供文件全名, 例如 -lxxx.lib. 路徑和文件名都需要指定.

默認(rèn)的 Include-paths, Library-paths 和 Libraries
可以通過cpp -v命令列出:
> cpp -v......#include "..." search starts here:#include <...> search starts here: /usr/lib/gcc/x86_64-pc-cygwin/6.4.0/include /usr/include /usr/lib/gcc/x86_64-pc-cygwin/6.4.0/../../../../lib/../include/w32api
在編譯時, 加入-v參數(shù)開啟verbose mode, 可以了解系統(tǒng)中使用到的庫路徑(-L)以及庫明細(xì)(-l)
> gcc -v -o hello.exe hello.c......-L/usr/lib/gcc/x86_64-pc-cygwin/6.4.0-L/usr/x86_64-pc-cygwin/lib-L/usr/lib-L/lib-lgcc_s     // libgcc_s.a-lgcc       // libgcc.a-lcygwin    // libcygwin.a-ladvapi32  // libadvapi32.a-lshell32   // libshell32.a-luser32    // libuser32.a-lkernel32  // libkernel32.a
Eclipse CDT 在 Eclipse CDT 中, 可以在項目上右鍵, 點擊project ⇒ Properties ⇒ C/C++ General ⇒ Paths and Symbols, 在標(biāo)簽頁"Includes", "Library Paths" and "Libraries"下, 設(shè)置 include path, library paths 和 libraries.

GCC環(huán)境變量
GCC 使用下列環(huán)境變量:
  • PATH: 用于搜索可執(zhí)行文件和運行時的動態(tài)鏈接庫(.dll, .so).
  • CPATH: 用于搜索頭文件包含路徑. 優(yōu)先級低于直接用-I<dir>指定的路徑. C_INCLUDE_PATH and CPLUS_INCLUDE_PATH可分別用于指定C和C++的頭文件路徑.
  • LIBRARY_PATH: 用于搜索庫文件的路徑, 優(yōu)先級低于用-L<dir>指定的路徑.

參考

評分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏1 分享淘帖 頂 踩
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復(fù) 返回頂部 返回列表
主站蜘蛛池模板: 国产粉嫩尤物极品99综合精品 | 精品视频一区二区在线观看 | 成人九区 | 精久久| 日韩欧美天堂 | 国产精品久久片 | 日韩一区二区三区视频 | 99成人 | 亚洲国产福利视频 | 殴美成人在线视频 | 成人免费视频网站在线观看 | 久久99视频| 黄色片免费看 | 四虎影院在线免费观看 | 国产成人精品a视频一区www | 91av在线电影 | 91国内精品久久 | 中文字字幕一区二区三区四区五区 | 免费视频一区 | 国产中文字幕在线观看 | 亚洲精品乱码久久久久久蜜桃91 | 久久久久九九九女人毛片 | 欧美综合一区二区三区 | 精品成人69xx.xyz | www国产成人免费观看视频,深夜成人网 | www.99re| 中文字幕精品一区二区三区在线 | 国产色片在线 | 久久99深爱久久99精品 | 国产成在线观看免费视频 | 91精品久久久久久久久中文字幕 | 久久中文字幕一区 | 91免费高清视频 | 精品国产精品 | wwww.8888久久爱站网 | 久久一区二区免费视频 | 午夜寂寞福利视频 | 亚洲免费人成在线视频观看 | 欧美精品v国产精品v日韩精品 | 不卡av在线 | 日韩有码在线播放 |