1. 程式人生 > >嵌入式之 C 語言編譯器(五)

嵌入式之 C 語言編譯器(五)

net DC 應用 不同 %s 翻譯 根據 oba 直接

我們在嵌入式的開發中經常會見到 GCC 和 gcc,那麽它們兩有何不同呢?GCC(GNU Compile Collection) 是指 GNU 編譯器集合,包含眾多語言的編譯器,如 C、C++、Java、D、Objective-C 等;而 gcc 則是特指 GCC 中的 C 語言編譯器。那麽 GCC 與嵌入式的關系是怎樣的呢?多數嵌入式操作系統都是基於 GCC 進行源碼編譯,如 Linux、VxWorks 以及 Android 等。在實際的開發中,內核相關的開發用的是 gcc,而應用開發用的是 gcc/g++/gdc 等。

下來我們來看看一個嵌入式開發中的高端大氣上檔次的詞語:交叉編譯。那麽為什麽會有交叉編譯呢?在以往的嵌入式設備往往都是資源受限的,不可能直接在嵌入式上直接對處理器進行編程。那麽此時的解決方案便是在開發主機(PC)上對源碼進行編譯,最終生成目標主機(嵌入式設備)的可執行程序。gcc 是如何進行交叉編譯的呢?1、配置目標主機的編譯工具鏈(如arm-linux);2、配置工具鏈的具體版本:根據具體的目標代碼選擇相應的工具鏈版本,正確使用關於硬件體系的特殊編譯選項

。下來我們來看看大型企業的嵌入式開發環境是怎樣的,如下

技術分享圖片

這個服務器集群相當於是我們自己公司的內部服務器,版本控制則是指由原來的版本經過我們一些代碼的修改之後產生的新版本,用於各個版本的控制的。文件追蹤則是指在服務器上面可以看到那部分的代碼是具體由哪個人進行改寫的,可具體到文件以及部分代碼。我們來看看編譯器是怎樣的,如下

技術分享圖片

編譯器其實是由預處理期、編譯器、匯編器以及鏈接器構成的。我們平時所說的由哪個編譯器編譯生成的文件,此時的編譯器便是指廣範圍的編譯器。那麽狹義上的編譯器則是指我們在平時所聽到的生產一個某語言的編譯器,此時的編譯器則是指將具體的語言翻譯成目標平臺代碼而已。我們來看看一個 .c 文件是怎樣編譯成 .o 文件的,具體步驟如下所示

技術分享圖片

我們看到並不是我們所想象的直接一步就由 .c 文件直接編譯成為 .o 可執行文件了,而是經過那麽多的步驟才會生成最終的可執行程序的。那麽此時便擴展一個問題,我們是如何理解“多語言混合開發”?我們在平時可能會聽到多語言混合開發,是指由好幾種語言混合進行一個應用程序開發的。那麽為什麽會產生這種混合的開發方式呢?比如說一個項目是由 C++ 完成的,但是其中的某些部分是可以通過 C# 完成的,此時精通 C++ 的人很少(相應工資就要的很高了),而 C# 的工程師由一大堆,我們就可以需要兩個精通 C++ 的工程師和好幾個 C# 的工程師來共同完成這個項目,達到以最小的開支完成此項目的效果。或者是你們小組內每個人擅長的語言方向不一樣,為了發揮每個人的最大效率便可以采取這種混合開發的方式。下來我們來看看幾種多語言混合開發的方式

方式一,如下

技術分享圖片

此方式是通過由幾種語言經過匯編得到目標平臺的匯編語言,再由目標平臺匯編器統一鏈接生成可執行程序。行業典型的案例就是 .net framework,它便是由 C#、C++ 以及 VB 混合開發得到的,如下

技術分享圖片

方式二,如下

技術分享圖片

它是由各自的語言生成相應的庫再通過目標平臺鏈接器統一鏈接為可執行程序。典型的案例便是 QQ 了,如下

技術分享圖片

方式三,如下

技術分享圖片

它是經過各自的編譯器先生成可執行程序 .exe,再通過進程間通信協議進而生成可執行程序。行業案例:Eclipse,如下

技術分享圖片

下來我們來看看 gcc 關鍵編譯選項。

gcc 關鍵編譯選項一a> 預處理指令是:gcc -E file.c -o file.i;b > 編譯指令:gcc -S file.i -o file.s;c> 匯編指令:gcc -c file.s -o file.o

下來我們來看看效果分別是怎樣的


func.h 源碼

#include <stdio.h>

void func()
{
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}


test.c 源碼

#include <stdio.h>
#include "func.h"

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[])
{
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    return 0;
}

我們來看看預處理的效果,打開 test.i 文件看看,開頭是這樣的

技術分享圖片

第一行的 1 表示下面的內容是屬於 test.c 文件的內容,下面是一些頭文件的包含。

技術分享圖片

# 2 "test.c" 2 的意思是 test.c 頭文件的包含已經結束了,# 1 "func.h" 1 表示 func.h 相關內容的開始。最後便是 test.c 文件 main 函數的內容了。下面看看編譯指令生成的 .s 文件

技術分享圖片

都是一些生成的匯編命令。下面來看看最後的匯編指令生成 .o 文件

技術分享圖片

gcc 關鍵編譯選項二:a> 生成映射文件:gcc -WI,-Map=test.map file.c;b> 宏定義:gcc -D'TEST="test"' file.c;c> 獲取系統頭文件路徑:gcc -v file.c

gcc 關鍵編譯選項三生成依賴關系。a> 獲取目標完整的依賴關系:gcc -M test.c;b> 獲取目標的部分依賴關系:gcc -MM test.c

下來我們來看看 -M 和 -MM 的效果分別是怎樣的,如下

技術分享圖片

我們看到包含了那麽多的頭文件,它的格式類似於 makefile 中的目標與依賴的關系。其中依賴是 test.c 和眾多的頭文件以及我們自己包含的 func.h 頭文件。再來看看 -MM 的效果

技術分享圖片

我們看到 -MM 的效果是指依賴於 test.c 和 func.h,並沒有那些別的頭文件。

gcc 關鍵編譯選項四:指定庫文件及庫文件搜索路徑。-L 選項是指定庫文件的搜索路徑;-l 是指定庫文件,如 gcc test.c -L -lfunc


func.c 源碼

#include <stdio.h>

void func()
{
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}


test.c 源碼

#include <stdio.h>

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[])
{
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    return 0;
}

編譯結果如下

技術分享圖片

我們看到經過 ar crs 命令將 func.o 打包成 libfunc.a 文件後,再通過 gcc test.c -L. -lfunc 命令生成可執行程序 a.out(其中 -L 後面的點代表在當前目錄下)。

嵌入式之 C 語言編譯器(五)