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

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2101|回復: 0
收起左側

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

[復制鏈接]
ID:912806 發表于 2021-8-26 11:41 | 顯示全部樓層 |閱讀模式
本帖最后由 michaelchain 于 2021-8-26 11:45 編輯

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

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. }
復制代碼

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

  3. #include "inc.h"

  4. #endif
復制代碼

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
復制代碼

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

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

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

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

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


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

2.編譯(Compilation)
這一步的編譯將經過預處理之后的程序轉換成特定匯編代碼的過程, 編譯的命令如下:
$ gcc -S -I./inc main.c -o obj/main.s
-S讓編譯器在編譯之后停止. 這一步會生成程序的匯編代碼, 內容如下:
        .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)
匯編過程將上一步的匯編代碼轉換成機器碼(machine code),這一步產生了二進制的目標文件, gcc匯編過程通過as命令完成
as obj/main.s -o obj/main.o# porgcc -c obj/main.s -o obj/main.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目錄下已經有main.o, func1.o和func2.o這三個目標文件, 現在需要通過linker將這些目標文以及所需的庫文件(.so等)鏈接成最終的可執行文件(executable file)
命令如下
gcc -o obj/main obj/main.o obj/func1.o obj/func2.o
這時候在obj目錄下就會生成可執行文件main

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

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

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

默認的 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參數開啟verbose mode, 可以了解系統中使用到的庫路徑(-L)以及庫明細(-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, 在標簽頁"Includes", "Library Paths" and "Libraries"下, 設置 include path, library paths 和 libraries.

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

參考

評分

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

查看全部評分

回復

使用道具 舉報

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

本版積分規則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 亚洲国产精品99久久久久久久久 | 99视频在线播放 | 福利一区二区 | 亚洲av一级毛片 | 欧美日韩视频在线 | 自拍第一页 | 欧美国产一区二区 | 在线免费黄色 | 欧美精品在线播放 | 久久一起草 | 在线观看中文字幕dvd播放 | 欧美一区视频 | 在线看av网址 | 国产真实精品久久二三区 | 亚洲国产一区二区三区四区 | 午夜午夜精品一区二区三区文 | 国内久久精品 | 国产成人精品一区二区三区 | 综合激情久久 | 精品久久久久一区二区国产 | h视频免费在线观看 | 人妖av| 91久久视频 | 久久精品亚洲精品国产欧美 | 色婷婷综合网站 | 精品一区av| 国产婷婷色一区二区三区 | 91精品国产日韩91久久久久久 | 精品国产乱码一区二区三区 | 午夜一区二区三区 | 日本中出视频 | 亚洲日韩中文字幕一区 | 成人欧美一区二区三区视频xxx | 男女视频在线观看免费 | 99精品视频在线观看 | 国产精品视频一区二区三区 | 亚洲欧美在线观看 | 91久久网站 | 亚洲精品无人区 | 亚洲一二三在线观看 | 色综合色综合网色综合 |