嵌入式開發】gcc 學習筆記(一) - 編譯C程序 及 編譯過程

      網友投稿 1058 2025-03-31

      一. C程序編譯過程

      編譯過程簡介 : C語言的源文件 編譯成 可執行文件需要四個步驟, 預處理 (Preprocessing) 擴展宏, 編譯 (compilation) 得到匯編語言, 匯編 (assembly) 得到機器碼, 連接 (linking) 得到可執行文件;

      -- 查看每個步驟的編譯細節 : "-E" 對應 預處理, "-S" 對應 編譯, "-c" 對應 匯編, "-O" 對應 連接;

      -- 每個步驟對應的工具 : 預處理器 (CPP - The C Preprogressor), 編譯器 (cc1), 匯編器 (as), 連接器 (ld);

      --?查看總體編譯細節?: 使用 "-v" 參數, 可以查看總體編譯細節;

      octopus@octopus:~/test$ gcc -v main.c 使用內建 specs。 COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.6/lto-wrapper 目標:i686-linux-gnu 配置為:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 線程模型:posix gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686' /usr/lib/gcc/i686-linux-gnu/4.6/cc1 -quiet -v -imultilib . -imultiarch i386-linux-gnu main.c -quiet -dumpbase main.c -mtune=generic -march=i686 -auxbase main -version -fstack-protector -o /tmp/ccUWUvbm.s GNU C (Ubuntu/Linaro 4.6.3-1ubuntu5) 版本 4.6.3 (i686-linux-gnu) 由 GNU C 版本 4.6.3 編譯, GMP 版本 5.0.2,MPFR 版本 3.1.0-p3,MPC 版本 0.9 GGC 準則:--param ggc-min-expand=100 --param ggc-min-heapsize=131072 忽略不存在的目錄“/usr/local/include/i386-linux-gnu” 忽略不存在的目錄“/usr/lib/gcc/i686-linux-gnu/4.6/../../../../i686-linux-gnu/include” #include "..." 搜索從這里開始: #include <...> 搜索從這里開始: /usr/lib/gcc/i686-linux-gnu/4.6/include /usr/local/include /usr/lib/gcc/i686-linux-gnu/4.6/include-fixed /usr/include/i386-linux-gnu /usr/include 搜索列表結束。 GNU C (Ubuntu/Linaro 4.6.3-1ubuntu5) 版本 4.6.3 (i686-linux-gnu) 由 GNU C 版本 4.6.3 編譯, GMP 版本 5.0.2,MPFR 版本 3.1.0-p3,MPC 版本 0.9 GGC 準則:--param ggc-min-expand=100 --param ggc-min-heapsize=131072 Compiler executable checksum: 09c248eab598b9e2acb117da4cdbd785 COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686' as --32 -o /tmp/cciJfMAd.o /tmp/ccUWUvbm.s COMPILER_PATH=/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../../lib/:/lib/i386-linux-gnu/:/lib/../lib/:/usr/lib/i386-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686' /usr/lib/gcc/i686-linux-gnu/4.6/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -z relro /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crt1.o /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.6/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.6 -L/usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu -L/usr/lib/gcc/i686-linux-gnu/4.6/../../../../lib -L/lib/i386-linux-gnu -L/lib/../lib -L/usr/lib/i386-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/i686-linux-gnu/4.6/../../.. /tmp/cciJfMAd.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-linux-gnu/4.6/crtend.o /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crtn.o

      1. 預處理

      預處理命令 : 源程序中 以 "#" 開頭的命令是 預處理命令, 如 "#include", "#define", "ifndef" 等;

      預處理過程 : 預處理將 include 的文件插入到 源文件中, 展開 define 宏定義, 根據條件 編譯代碼;

      編譯下面的源程序?:

      /*************************************************************************

      > File Name: main.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月30日 星期三 17時31分08秒

      ************************************************************************/

      #include

      #define NUM 5

      int main(int argc, char **argv)

      {

      printf("Hello World ! num = %d \n", NUM);

      return 0;

      }

      預處理結果 : 預處理 源程序 產生的結果會放到 ".i" 后綴的文件中, 默認情況下 ".i" 后綴文件是不寫到磁盤中的, 如果加上 "-save-temps" 參數, 就會將所有的中間文件都保存到磁盤中;

      -- 分析下面的例子 : 使用?gcc -save-temps main.c 命令編譯源程序, 所有的中間文件都會保留, main.i 是預處理結果, main.s 是編譯結果, main.o 是匯編結果, a.out 是連接生成的可執行文件;

      octopus@octopus:~/test$ ls main.c octopus@octopus:~/test$ gcc -save-temps main.c octopus@octopus:~/test$ ls a.out main.c main.i main.o main.s octopus@octopus:~/test$ ./a.out Hello World ! num = 5

      查看預處理細節 : 使用 gcc -E mian.c 命令, 會輸出編譯細節, 打印出上千行, 這里只貼出部分;

      octopus@octopus:~/test$ gcc -E main.c # 1 "main.c" # 1 "" # 1 "<命令行>" # 1 "main.c" ... # 1 "/usr/include/stdio.h" 1 3 4 # 28 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 324 "/usr/include/features.h" 3 4 ... # 9 "main.c" 2 int main(int argc, char **argv) { printf("Hello World ! \n"); return 0; }

      在 gcc 命令行中進行宏定義 : 使用 gcc -DNUM=5 main.c 命令, 在程序中就可以使用 NUM 宏定義了, "-DNUM" 相當于在程序中定義了 "#define NUM 5";

      -- main.c 內容 :

      /*************************************************************************

      > File Name: main.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月30日 星期三 17時31分08秒

      ************************************************************************/

      #include

      int main(int argc, char **argv)

      {

      printf("Hello World ! num = %d \n", NUM);

      return 0;

      }

      octopus@octopus:~/test$ gcc -DNUM=5 main.c octopus@octopus:~/test$ ./a.out Hello World ! num = 5

      2. 編譯

      編譯流程 : 編譯器在編譯階段依次執行 詞法分析, 語法分析, 代碼優化, 存儲分配, 代碼生成 五個步驟;

      -- 多次掃描方案 : 編譯器每次掃描代碼只完成一項工作, 如 第一次掃描 只進行詞法分析, 第二次掃描進行 語法分析, 掃描多次完成上面的五個步驟;

      生成中間的匯編中間文件 : 使用 gcc -S main.c 編譯上面的 main.c 源程序, 可以得到 mian.s 匯編語言文件, 這是產生的中間匯編程序;

      -- 編譯過程 及 結果 :

      octopus@octopus:~/test$ gcc -S main.c octopus@octopus:~/test$ cat main.s .file "main.c" .section .rodata .LC0: .string "Hello World ! num = %d \n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl , %esp movl $.LC0, %eax movl , 4(%esp) movl %eax, (%esp) call printf movl

      octopus@octopus:~/test$ gcc -S main.c octopus@octopus:~/test$ cat main.s .file "main.c" .section .rodata .LC0: .string "Hello World ! num = %d \n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp movl $.LC0, %eax movl $5, 4(%esp) movl %eax, (%esp) call printf movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits octopus@octopus:~/test$

      , %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits octopus@octopus:~/test$

      3. 匯編

      匯編過程 : 匯編 就是將 匯編語言代碼 翻譯成 機器碼, 也就是 ".o" 后綴的對象文件, 該過程 使用 匯編器 as 實現;

      獲取中間文件 : "-c" 選項可以保留 匯編過程中的 ".o" 后綴的中間文件, 使用 gcc -c main.c 命令, 可以獲得 main.o 對象文件;

      octopus@octopus:~/test$ ls main.c octopus@octopus:~/test$ gcc -c main.c octopus@octopus:~/test$ ls main.c main.o

      4. 連接

      鏈接過程 : 使用 ld 連接器, 將 匯編 過程中生成的 ".o" 對象文件, 與其它 對象文件 和 庫文件連接起來, 生成可執行的二進制文件;

      連接示例 : 使用 gcc main.o 將匯編過程生成的對象文件 main.o , 生成可執行文件 a.out ;

      octopus@octopus:~/test$ gcc main.o octopus@octopus:~/test$ ./a.out Hello World ! num = 5

      二. 編譯C程序

      1. 編譯單個C程序

      C語言程序示例 : 簡單的Hello World;

      /*************************************************************************

      > File Name: main.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月19日 星期六 16時22分26秒

      ************************************************************************/

      #include

      int main(int argc, char **argv)

      {

      printf("Hello World! \n");

      return 0;

      }

      octopus@octopus:~/gcc$ gcc main.c octopus@octopus:~/gcc$ ./a.out Hello World!

      -- 命令 : gcc main.c -o main;

      octopus@octopus:~/gcc$ gcc main.c -o main octopus@octopus:~/gcc$ ./main Hello World!

      顯示警告選項 : -Wall 選項, 可以在編譯的時候, 將警告信息輸出到終端中;

      -- 編譯輸出警告信息 : gcc -Wall main.c;

      人為制造警告 : 在 printf 輸出的時候, 使用 %s 作為一個 int 數據的占位符;

      /*************************************************************************

      > File Name: main.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月19日 星期六 16時22分26秒

      ************************************************************************/

      #include

      int main(int argc, char **argv)

      {

      printf("Hello World! num = %s\n", 4);

      return 0;

      }

      octopus@octopus:~/gcc$ gcc main.c main.c: 在函數‘main’中: main.c:12:2: 警告: 格式 ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat] octopus@octopus:~/gcc$ ./a.out 段錯誤 (核心已轉儲)

      2. 編譯多個文件

      由三個文件組成的程序 : kill.h, kill.c, main.c, 其中 main.c 是主函數入口, 調用 kill.c 定義的方法;

      -- kill.h 內容 : 聲明 kill 方法, 引用了該頭文件, 即可使用 kill 方法;

      【嵌入式開發】gcc 學習筆記(一) - 編譯C程序 及 編譯過程

      /*************************************************************************

      > File Name: kill.h

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月19日 星期六 20時51分59秒

      ************************************************************************/

      #ifndef KILL

      int kill(char *);

      #endif

      /*************************************************************************

      > File Name: kill.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月19日 星期六 20時53分53秒

      ************************************************************************/

      #include

      int kill(char *ch)

      {

      printf("%s \n", ch);

      return 0;

      }

      /*************************************************************************

      > File Name: main.c

      > Author: octopus

      > Mail: octopus_work.163.com

      > Created Time: 2014年04月19日 星期六 16時22分26秒

      ************************************************************************/

      #include

      #include"kill.h"

      int main(int argc, char **argv)

      {

      printf("Hello World! \n");

      kill("fuck");

      return 0;

      }

      引用頭文件庫符號區別 : #include"kill.h" #include ;

      -- #include "kill.h" : 先在當前目錄搜索 kill.h 頭文件, 在到系統中搜索該頭文件;

      -- #include : 直接去系統庫中尋找頭文件, 不會搜索當前目錄;

      編譯文件 : 使用 gcc -Wall main.c kill.c -o kill 進行編譯;

      octopus@octopus:~/gcc$ gcc -Wall main.c kill.c -o kill octopus@octopus:~/gcc$ ./kill Hello World! fuck

      3. 獨立編譯文件

      開發需求 : 當一個項目比較大的時候, 整個項目編譯時間會很長, 如果改變一個函數就需要重新編譯整個項目, 就會很浪費時間;

      -- 解決方案 : 程序被存儲在多個源文件中, 每個源文件都單獨進行編譯;

      單獨編譯多個源文件步驟 : ?首先生成 對象文件, 再將對象文件鏈接生成可執行文件;

      -- 編譯對象文件 : 將源程序編譯成不可執行的文件, 生成 .o 后綴的對象文件;

      -- 鏈接程序 : gcc 中有一個鏈接器將所有的對象文件鏈接到一起, 生成一個可執行文件;

      解析對象文件 : 文件中存放的是機器碼, 機器碼中對其他文件中的 函數 或者 變量引用的地址沒有解析, 當鏈接程序的時候才將這些地址寫入;

      生成對象文件 : -c 參數用于生成 對象文件;

      -- 生成kill.o對象文件?: gcc -Wall -c kill.c , 會生成 kill.o 文件, 該對象文件中引用 kill 方法, 該方法對應的地址沒有被解析;

      octopus@octopus:~/gcc$ gcc -Wall -c kill.c octopus@octopus:~/gcc$ ls kill.c kill.h kill.o main.c

      octopus@octopus:~/gcc$ gcc -Wall -c main.c octopus@octopus:~/gcc$ ls kill.c kill.h kill.o main.c main.o

      鏈接對象文件 : gcc main.o kill.o -o main 命令, 鏈接 main.o 和 kill.o 兩個對象文件;

      -- 不許要-Wall參數 : 鏈接程序只有兩種結果, 成功 或者 失敗, 不許要警告信息了;

      -- 鏈接器 : gcc中ld鏈接器 用來鏈接對象文件;

      octopus@octopus:~/gcc$ gcc main.o kill.o -o main octopus@octopus:~/gcc$ ./main Hello World! fuck

      對象文件的鏈接次序 : 大部分編譯器都可以隨意排列順序, 但是有的編譯器需要注意鏈接次序;

      -- 編譯器和連接器次序 : 編譯器和鏈接器搜索外部函數 是 從左到右進行查找;

      -- 文件次序 : 調用函數的 對象文件, 該文件應該先于 定義函數的 對象文件, 這里 main.o 應該在 kill.o 之前;

      -- 錯誤排查 : 如果在編譯程序的時候, 列出了所有的文件, 但是還出現了 未定義 錯誤, 就需要注意 文件排列的問題;

      修改文件流程 : 當修改了一個文件之后, 只需要 重新編譯這個文件即可, 之后將這個新編譯的對象文件 與 原來的對象文件進行鏈接, 即可生成新的可執行文件;

      -- 重新編譯 : 當修改了一個文件之后, 只需要將這個文件重新編譯成 對象文件即可;

      -- 重新鏈接 : 將新編譯的對象文件, 與之前已經編譯好的 其它源文件的對象文件進行鏈接即可;

      ,

      gcc 嵌入式

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:OKR工作法(一)什么是OKR,OKR與KPI的區別
      下一篇:excel打印預覽空白怎么辦(excel預覽正常打印出來空白)
      相關文章
      亚洲久悠悠色悠在线播放| 国产成人精品亚洲一区| 亚洲香蕉久久一区二区| 亚洲宅男永久在线| 亚洲国产成人片在线观看| 中文字幕日韩亚洲| 国产成人综合亚洲亚洲国产第一页| 亚洲国产V高清在线观看| 亚洲色大成网站www久久九| 国产精品亚洲片夜色在线| 精品日韩99亚洲的在线发布| 亚洲专区中文字幕| 亚洲av无码不卡久久| 亚洲成a人片在线看| 久久亚洲精品国产亚洲老地址| 国产人成亚洲第一网站在线播放| 亚洲av无码一区二区三区天堂古代| 亚洲人成在线中文字幕| 亚洲av无码一区二区三区天堂古代 | 国产亚洲一区二区三区在线不卡| 亚洲黄片毛片在线观看| 亚洲乱码国产一区网址| 三上悠亚亚洲一区高清| 亚洲精品白浆高清久久久久久 | 一本色道久久88亚洲精品综合| 亚洲精品美女网站| 亚洲欧美日韩中文字幕在线一区| 色偷偷噜噜噜亚洲男人| 亚洲电影日韩精品| 中文字幕精品亚洲无线码一区| 亚洲精品成人网站在线观看| 亚洲小视频在线观看| 亚洲午夜国产精品| 亚洲码和欧洲码一码二码三码| 理论亚洲区美一区二区三区| 亚洲精品偷拍视频免费观看| 亚洲精品乱码久久久久久久久久久久 | 亚洲精品欧美综合四区| 国产精品亚洲一区二区无码| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 精品亚洲视频在线观看|