在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

【gcc編譯優化系列】static與inline的區別與聯系

嵌入式物聯網開發 ? 來源:嵌入式物聯網開發 ? 作者:嵌入式物聯網開發 ? 2022-07-11 09:08 ? 次閱讀

1 問題來源

今天偶然留意到RT-Thread論壇的一個問題帖子,它的題目是RTT-VSCODE插件編譯RTT工程與RTT Studio結果不符,這種編譯問題是我最喜歡深扒的,于是我點進去看了看。

得知,它的核心問題就是有一個類似這樣定義的函數(為了簡要說明問題,我精簡了代碼):

/* main.c */

inline void test_func(int a, int b)
{
    printf("%d, %d\n", a, b);
}

int main(int argc, const char *argv[])
{
    /* do something */

    /* call func */
    test_func(1, 2);

    return 0;
}

然后,問題就是 同一套工程代碼在RT-Thread Studio上能夠編譯通過,但在VSCODE上卻產生錯誤,這個錯誤居然是undefined reference to ‘test_func’。

2 問題分析

看到undefined reference to ‘testfunc’這個錯誤,熟悉C代碼編譯流程的都知道,這是一個典型的鏈接錯誤,也就是說錯誤發在鏈接階段,鏈接錯誤的原因是找不到testfunc函數的實現體。

相信你一定也有許多問號??????

test_func不是定義在main.c里面嗎?????

不就在main函數的上面嗎??????

怎么可能會發生鏈接錯誤呢??????

我們平時寫函數不就是這樣寫的嗎??????

難道這個inline作妖??????

3 知識點分析

3.1 inline關鍵字是干嘛的?

準確來說,它這個inline是一個C++關鍵字,在函數聲明或定義中,函數返回類型前加上關鍵字inline,即可以把函數指定為內聯函數。但是由于市面上的大部分C編譯器都可以兼容部分C++的關鍵字和語法,所以我們也經常見到inline出現在C代碼中。

3.2 inline與宏定義有什么區別?

  1. 宏定義發生在預編譯處理階段,它僅僅是做字符串的替換,沒有任何的語法規則檢查,比如類型不匹配,宏展開后的各種語法問題,的確讓人比較頭疼;
  2. inline函數則是發生在編譯階段,有完整的語法檢查,在Debug版本中也可以跟普通函數一樣,正常打斷點進行調試;
  3. 由于處理的階段不一樣,這就導致如果宏函數展開后仍然是一個函數調用的話,它是具有調用函數的開銷,包括函數進棧出棧等等;而inline函數卻僅僅是函數代碼的拷貝替換,并不會發生函數調用的開銷,在這一點上inline具有很高的執行效率。

3.3 inline函數與普通函數有什么區別?

正如上面提及的,普通函數的調用在匯編上有標準的 push 壓實參指令,然后 call 指令調用函數,給函數開辟棧幀,函數運行完成,有函數退出棧幀的過程;而 inline 內聯函數是在編譯階段,在函數的調用點將函數的代碼展開,省略了函數棧幀開辟回退的調用開銷,效率高。

3.4 static函數與普通函數有什么區別?

兩者唯一的區別在于可見范圍不一樣:

  1. 不被static關鍵字修飾的函數,它在整個工程范圍內,全局都可以調用,即其屬性是global的;只要函數參與了編譯,且最后鏈接的時候把函數的.o文件鏈接進去了,是不會報undefined reference to ‘xxx’的;
  2. 被static關鍵字修飾的函數,只能在其定義的C文件內可見,即其屬性由global變成了local,這個時候如果有另一個C文件的函數想調用這個static的函數,那么對不起,最終鏈接階段會報undefined reference to ‘xxx’錯誤的。

4 解決方案

回到前文的問題,該如何解決這個問題呢?我的想法,有兩種解決思路:

4.1 放棄inline函數的優勢,將inline函數修改為普通函數

這個方法很簡單,無非就是去掉inline,做個降維處理,把inline函數變成普通函數,自然編譯鏈接就不會報錯。但我想,既然寫代碼的原作者加了inline,肯定是希望用上inline的高效率的特性,所以去掉inline顯然不是一個明智的選擇。

4.2 對inline函數加上static修飾

這一個做法,就可以很聰明地把它的問題給解決了。一個函數被static和inline修飾,證明這個函數是一個靜態的內聯函數,它的可見范圍依然是當前C文件,且同時具備inline函數的特性。

5 知其然且知其所以然

5.1 實踐出真理

為了驗證4.2的改法是否有效, 我在rt-thread/bsp/qemu-vexpress-a9中快速做個驗證,只需要在applications/main.c里面添加下面的測試代碼:

/* applications/main.c */
static inline void test_func(int a, int b)
{
  printf("%d, %d\n", a, b);
}

int main(void)
{
    printf("hello rt-thread\n");

    test_func(1, 2);

    return 0;
}

特此說明下,我使用的交叉編譯鏈是:gcc-arm-none-eabi-5_4-2016q3/bin/arm-none-eabi-gcc

然后使用scons編譯,果然編譯成功了,運行rtthread.elf,功能一切正常。

而當我去掉static的時候,期望中的鏈接錯誤果然出現了。

LINK rtthread.elf
build/applications/main.o: In function `main':
/home/recan/win_share_workspace/rt-thread-share/rt-thread/bsp/qemu-vexpress-a9/applications/main.c:253: undefined reference to `test_func'
collect2: error: ld returned 1 exit status
scons: *** [rtthread.elf] Error 1
scons: building terminated because of errors.

為了做進一步驗證,我在rtconfig.py里面的CFLAGS加了一個編譯選項:-save-temps=obj;這個選項的作用就是在編譯的過程中,把中間過程文件也同步輸出,這里的中間文件有以下幾個:

xxx.o 文件:這是最終對應單個C文件生成的二進制目標文件,這個文件是最終參與鏈接成可執行文件的。

xxx.s 文件:這是由預編譯處理后的xxx.i文件編譯得到的匯編文件,里面描述的是匯編指令;

xxx.i 文件:這是預編譯處理之后的文件,比如想宏定義被展開之后是怎么樣的,就可以看這個文件;

關于使用GCC編譯C程序的完整過程這個話題,我已經整理出來了,分享分享給大家,畢竟這個知識點,對于解決編譯問題可是幫助非常大的。

5.2 實踐結果分析

為了做對比,我把整個編譯執行了兩次,一次是加上static的,一次是不加static的;

5.2.1 .i文件對比

對比結果如下,使用的是linux下的diff命令

diff ./build/applications/main.i.nostatic ./build/applications/main.i.static
4516c4516
<             inline void test_func(int a, int b)
---
> static inline void test_func(int a, int b) 

結果我們發現如我們期望一樣,nostatic的僅比static的少了一個static修飾符,其他都是一樣的。

5.2.2 .s文件對比

.s文件使用文本對比工具,發現加了static的.s文件,里面有test_func的匯編實現代碼,而不加的這個函數直接就被優化掉了,壓根就找不到它的實現。

5.2.3 .o文件對比

由于.o文件已經不是可讀的文本文件了,我們只能通過一些命令行工具來查看,這里推薦linux命令行下的nm工具,具體用途和方法可以使用man nm查看下。這里直接給出對比的命令行結果:

nm -a ./build/applications/main.o.nostatic | grep test_func
         U test_func

nm -a ./build/applications/main.o.static | grep test_func  
000002d8 t test_func 

OK,從中已經可以看到重要區別了:在不帶static的版本中,main.c里定義的testfunc函數被認為是一個外部函數(標識為U),而被static修飾的卻是本地實現函數(標識為T)。 而標識為U的函數是需要外部去實現的,這也就解釋了為何nostatic的版本會報undefined reference to 'testfunc' 錯誤,因為壓根就沒有外部的誰去實現這個函數。

5.4 終極實驗

5.4.1 補充測試代碼

為了驗證好這幾個關鍵字的區別,以及為何加了inline還不內聯,如何才能真正的內聯,我補充了一下測試代碼:

#include 

#if 0
/* only inline function : link error ! */
inline void test_func(int a, int b)
{
    printf("%d, %d\n", a, b);
}
#endif

/* normal function: OK */
void test_func1(int a, int b)
{
    printf("%d, %d\n", a, b);
}

/* static function: OK */
static void test_func2(int a, int b)
{
    printf("%d, %d\n", a, b);
}

/* static inline function: OK, but no real inline */
static inline void test_func3(int a, int b)
{
    printf("%d, %d\n", a, b);
}

/* always_inline is very important*/
#define FORCE_FUNCTION  __attribute__((always_inline))

/* static inline function: OK, it real inline. */
FORCE_FUNCTION static inline void test_func4(int a, int b)
{
    printf("%d, %d\n", a, b);
}

int main(int argc, const char *argv[])
{
    printf("Hello world !\n");

    /* call these functions with the same input praram */
    //test_func(1, 2);
    test_func1(1, 2); // normal
    test_func2(1, 2); // static
    test_func3(1, 2); // static inline (real inline ?)
    test_func4(1, 2); // static inline (real inline ?)

    return 0;
}

5.4.2 編譯驗證

執行編譯

gcc main.c -save-temps=obj -Wall -o test_static -Wl,-Map=test_static.map

成功編譯,運行也完全沒有問題。

./test_static 
Hello world !
1, 2
1, 2
1, 2
1, 2

5.4.3 進階分析

通過上面的章節,我們可以知道,我們應該重點分析.s文件和.o文件,因為.o文件不可讀,我們用nm-a查看下:

 nm -a test_static.o | grep test_func
0000000000000000 T test_func1
000000000000002e t test_func2
000000000000005c t test_func3

結果發現test_func4不在里面了,看樣子是被真正inline了? 我們打開.s文件確認下:

    .file   "main.c"
    .text
    .section    .rodata
.LC0:
    .string "%d, %d\n"
    .text
    .globl  test_func1
    .type   test_func1, @function
test_func1:
.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
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   test_func1, .-test_func1
    .type   test_func2, @function
test_func2:
.LFB1:
    .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
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   test_func2, .-test_func2
    .type   test_func3, @function
test_func3:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   test_func3, .-test_func3
    .section    .rodata
.LC1:
    .string "Hello world !"
    .text
    .globl  main
    .type   main, @function
main:
.LFB4:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movl    %edi, -20(%rbp)
    movq    %rsi, -32(%rbp)
    leaq    .LC1(%rip), %rdi
    call    puts@PLT
    movl    $2, %esi
    movl    $1, %edi
    call    test_func1
    movl    $2, %esi
    movl    $1, %edi
    call    test_func2
    movl    $2, %esi
    movl    $1, %edi
    call    test_func3
    movl    $1, -8(%rbp)
    movl    $2, -4(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    movl    %eax, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    nop
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE4:
    .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:

從中,我們可以看到testfunc1與testfunc2的區別是testfunc1是GLOBAL的,而testfunc2是LOCAL的;而testfunc2與testfunc3卻是完全一模一樣;也就是說testfunc3使用static inline壓根就沒有被內聯。 我們再找找testfunc4,發現已經找不到了,到底是不是內聯了?我們再看看main函數里面調用的部分:

main:
.LFB4:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movl    %edi, -20(%rbp)
    movq    %rsi, -32(%rbp)
    leaq    .LC1(%rip), %rdi
    call    puts@PLT

    movl    $2, %esi
    movl    $1, %edi
    call    test_func1  //調用test_func1函數

    movl    $2, %esi
    movl    $1, %edi
    call    test_func2  //調用test_func2函數

    movl    $2, %esi
    movl    $1, %edi
    call    test_func3  //調用test_func3函數

    movl    $1, -8(%rbp)
    movl    $2, -4(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    movl    %eax, %esi
    leaq    .LC0(%rip), %rdi
    movl    $0, %eax
    call    printf@PLT
    nop
    movl    $0, %eax
    leave               //“調用”test_func4函數,使用了內聯,直接拷貝了代碼,并不是真的函數調用。


    .cfi_def_cfa 7, 8

嘩,果然,這才是真正的內聯啊,我們終于揭開了這個神秘的面紗。

5.4 實踐經驗總結

  • inline有利有弊,切記使用的時候,最好讓它跟static一起使用,否則可能導致的問題超出你的想象。
  • 加了inline,不是你想內聯,編譯器就一定會幫你內聯的,還得看代碼的實現。
  • 如果要強制內聯,還得加參數修飾,每個C編譯器的方法還不一樣,比如gcc的是使用_attribute((alwaysinline))修飾定義的函數即可。

6 更多分享

本項目的所有測試代碼和編譯腳本,均可以在我的github倉庫01workstation中找到,歡迎指正問題。

歡迎關注我的github倉庫01workstation,日常分享一些開發筆記和項目實戰,歡迎指正問題。

同時也非常歡迎關注我的CSDN主頁和專欄:

【CSDN主頁:架構師李肯】

【RT-Thread主頁:架構師李肯】

【C/C++語言編程專欄】

【GCC專欄】

信息安全專欄】

【RT-Thread開發筆記】

有問題的話,可以跟我討論,知無不答,謝謝大家。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • GCC
    GCC
    +關注

    關注

    0

    文章

    107

    瀏覽量

    24857
  • static
    +關注

    關注

    0

    文章

    33

    瀏覽量

    10383
  • inline
    +關注

    關注

    0

    文章

    4

    瀏覽量

    1642
收藏 人收藏

    評論

    相關推薦

    GCC編譯優化系列】前后編譯的兩版本固件bin大小不一樣?

    GCC編譯優化系列】前后編譯的兩個版本固件bin大小不一樣,怎么辦?
    的頭像 發表于 09-09 09:01 ?4809次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優化</b><b class='flag-5'>系列</b>】前后<b class='flag-5'>編譯</b>的兩版本固件bin大小不一樣?

    Linux 下GCC編譯

    一、Linux 下多文件編譯 在上一篇 Linux 下的 C 編程我們知道了 Linux 下的編譯器為 GCC ,以及如何使用 GCC 進行編譯
    的頭像 發表于 09-11 15:18 ?2674次閱讀
    Linux 下<b class='flag-5'>GCC</b>的<b class='flag-5'>編譯</b>

    使用gcc編譯優化與不優化問題

    同樣的程序,使用gcc編譯優化與不優化的結果不一代碼如下:1. #include 2.3. int main()4. {5.int i = 1;6.7.i
    發表于 09-27 10:33

    請問static inline有什么作用?

    其實統一接口,大家都需要實現這個接口,如果不用static,那就很有可能重名。編譯就會出錯了。這里的接口 指什么呢?? 可以截圖看么??static inline 是內聯的:小函數,而
    發表于 04-28 06:56

    編譯cmsis_gcc.h文件時有上百個警告信息是怎么回事?

    編譯時 cmsis_gcc.h 這個文件有上百個警告信息,提示的警告信息都一樣,不知道是怎么回事,請問這個警告信息是為什么?能不能修改一下給消除掉#define __STATIC_FORCEINLINE __attribute
    發表于 05-09 09:54

    【原創精選】RT-Thread征文精選技術文章合集

    默認的鏈接腳本【GCC編譯優化系列staticinline
    發表于 07-26 14:56

    AVR系列單片機GCC免費編譯工具

    AVR系列單片機GCC免費編譯工具
    發表于 04-13 15:23 ?54次下載

    淺談gcc編譯

    3.3 gcc編譯器 GNU CC(簡稱為gcc)是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語言
    發表于 10-18 13:48 ?0次下載

    常見gcc編譯警告整理以及解決方法

     GCC有很多的編譯選項,警告選項;指定頭文件、庫路徑;優化選項。本文針整理一下GCC的警告選項以及gcc
    發表于 11-14 11:19 ?2.1w次閱讀

    GCC編譯優化指南

    (cpp) → 編譯(gcc或g++) → 匯編(as) → 連接(ld) ;括號中表示每個階段所使用的程序,它們分別屬于 GCC 和 Binutils 軟件包。顯然的,優化應當從
    發表于 04-02 14:36 ?555次閱讀

    gcc編譯優化系列】如何獲取gcc默認的鏈接腳本

    我們都知道在一般的嵌入式開發中,使用gcc編譯固件的一般流程是,先把所有的.c文件和.s文件編譯成.o文件,然后把所有的.o文件鏈接成一個elf文件,最后由elf文件導出bin文件。 那么在鏈接成
    的頭像 發表于 07-11 09:15 ?3781次閱讀

    GCC編譯優化系列】實戰分析C代碼遇到的編譯問題及解決思路

    GCC編譯優化系列】實戰分析C工程代碼可能遇到的編譯問題及其解決思路
    的頭像 發表于 07-10 23:15 ?1459次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優化</b><b class='flag-5'>系列</b>】實戰分析C代碼遇到的<b class='flag-5'>編譯</b>問題及解決思路

    GCC編譯優化系列】multiple-definition

    GCC編譯優化系列】這種讓人看不懂的multiple-definition真的有點讓人頭疼
    的頭像 發表于 07-11 09:26 ?7299次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優化</b><b class='flag-5'>系列</b>】multiple-definition

    GCC編譯優化系列】-specs=kernel.specs

    GCC編譯優化系列GCC編譯鏈接時候--specs=kernel.specs鏈接屬性究竟是個
    的頭像 發表于 07-11 09:25 ?3534次閱讀
    【<b class='flag-5'>GCC</b><b class='flag-5'>編譯</b><b class='flag-5'>優化</b><b class='flag-5'>系列</b>】-specs=kernel.specs

    LL庫中常見關鍵字__STATIC_INLINE

    LL庫中常見關鍵字__STATIC_INLINE,其定義見cmsis_gcc.h
    的頭像 發表于 07-24 11:30 ?1499次閱讀
    LL庫中常見關鍵字__<b class='flag-5'>STATIC_INLINE</b>
    主站蜘蛛池模板: 日本三级高清| 性欧美丰满xxxx性久久久| 四虎最新在线| 成人午夜大片免费7777| 毛片在线播放网址| 亚洲夜夜爱| 天天摸天天躁天天添天天爽| 成 人 色综合| 尻美女视频| 极品啪啪| www懂爱| 天天伊人网| 国产午夜精品久久理论片小说| 天天艹夜夜艹| 免免费看片| 激情六月丁香婷婷| 日本欧美一区二区三区免费不卡| 看5xxaaa免费毛片| 亚洲码欧美码一区二区三区| 综合伊人| 色激情网| 国产亚洲人成网站观看| 狠狠色噜噜狠狠狠狠色综合久| 国产精品美女免费视频观看| 午夜秒播| 69色视频| 高h办公室| 秋霞一级特黄真人毛片| 日本国产在线观看| 久草婷婷| 天天综合亚洲国产色| 成人爽a毛片在线视频| 婷婷国产| www一区二区三区| 美女三级黄| 四虎国产精品永久在线看| 麻豆国产三级在线观看| 亚洲va欧美va国产综合久久| 97天天操| 伊人久久影院大香线蕉| 性欧美人与zooz|