那曲檬骨新材料有限公司

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

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

3天內不再提示

【gcc編譯優化系列】如何(不)回收未發生調用的函數

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

1 問題場景

大家都知道,我們在開發單片機類的嵌入式固件時,一般使用的FLASH存儲空間都是比較有限的,小的可能幾十KB,大一點的可能也就幾百KB,可以說是寸金寸土的FLASH空間,可容不得我們半點垃圾代碼。 如果我們在寫代碼的過程中,隨便寫一些沒用的代碼,比如一些測試代碼,最后版本釋放的時候,這些測試代碼又沒有刪掉,還是參與了編譯,那么勢必最后這個函數的代碼實現就會保留在我們的固件包里面,這樣我們的固件包的bin文件大小勢必會增加,這顯然不是我們想要的。 另外,還有一種場景下,有些函數我們使用static修飾的局部函數,只在初始化的時候通過初始化列表的形式調用一下,比如RT-Thread的初始化實現,INIT_DEVICE_EXPORT(device_init_func),那么我們是不希望這個函數被優化掉的,否則最后會出邏輯問題。 在使用GCC作為編譯器的環境下,有什么辦法可以實現呢?

2 需求分析

這里的需求兩點:

  1. 沒有被調用的函數需要移除,不出現在最后的固件文件里面;
  1. 某些特殊的函數實現,沒有被顯式調用,但是需要保留它,不能被優化掉。

3 需求實現

3.1 示例代碼

實現的一個示例代碼如下所示,功能很簡單就定義了2個沒被調用的函數,一個我希望優化移除,一個我希望被優化保留。

#include 

#define CODE_SECTION(x)               __attribute__((section(x)))
#define CODE_KEEP_USED                CODE_SECTION(".text.keep.used.code")

void unused_func1(int a)
{
    printf("a: %d\n", a);
}

CODE_KEEP_USED void unused_func2(int a)
{
    printf("a: %d\n", a);
}

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

3.2 鏈接腳本

鏈接腳本是GCC在鏈接所有目標文件變成可執行文件的時候,需要讀取的一個配置文件,該文件決定了最后的可執行文件是如何分布的。 我這里使用的是Ubuntu X64平臺 GCC默認的鏈接腳本,由此改造而來。 至于如何獲取GCC默認的鏈接腳本,請參考這里的教程。 修改后的鏈接腳本如下:

/* Script for -z combreloc -z separate-code */
/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
          "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id  : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
  .rela.dyn       :
    {
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
      *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
      *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
      *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
      *(.rela.ifunc)
    }
  .rela.plt       :
    {
      *(.rela.plt)
      PROVIDE_HIDDEN (__rela_iplt_start = .);
      *(.rela.iplt)
      PROVIDE_HIDDEN (__rela_iplt_end = .);
    }
  . = ALIGN(CONSTANT (MAXPAGESIZE));
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  }
  .plt            : { *(.plt) *(.iplt) }
.plt.got        : { *(.plt.got) }
.plt.sec        : { *(.plt.sec) }
  .text           :
  {
    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(SORT(.text.sorted.*))
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf.em.  */
    *(.gnu.warning)
  }
  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  }
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  . = ALIGN(CONSTANT (MAXPAGESIZE));
  /* Adjust the address for the rodata segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gnu_extab      : ONLY_IF_RW { *(.gnu_extab) }
  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata      :
   {
     PROVIDE_HIDDEN (__tdata_start = .);
     *(.tdata .tdata.* .gnu.linkonce.td.*)
   }
  .tbss          : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array    :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array    :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array    :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }

  /* Here use to keep some sections, which are not wanted to be removed. */
  .text_keep_used_code :
  {
    KEEP (*(.text.keep.used.code))
  }

  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { KEEP (*(.jcr)) }
  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { *(.dynamic) }
  .got            : { *(.got) *(.igot) }
  . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
  .got.plt        : { *(.got.plt) *(.igot.plt) }
  .data           :
  {
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  .bss            :
  {
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we do not
      pad the .data section.  */
   . = ALIGN(. != 0 ? 64 / 8 : 1);
  }
  .lbss   :
  {
    *(.dynlbss)
    *(.lbss .lbss.* .gnu.linkonce.lb.*)
    *(LARGE_COMMON)
  }
  . = ALIGN(64 / 8);
  . = SEGMENT_START("ldata-segment", .);
  .lrodata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
  {
    *(.lrodata .lrodata.* .gnu.linkonce.lr.*)
  }
  .ldata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
  {
    *(.ldata .ldata.* .gnu.linkonce.l.*)
    . = ALIGN(. != 0 ? 64 / 8 : 1);
  }
  . = ALIGN(64 / 8);
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { *(.stab) }
  .stabstr       0 : { *(.stabstr) }
  .stab.excl     0 : { *(.stab.excl) }
  .stab.exclstr  0 : { *(.stab.exclstr) }
  .stab.index    0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment       0 : { *(.comment) }
  .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { *(.debug_macro) }
  .debug_addr     0 : { *(.debug_addr) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}

關鍵地方在于.textkeepused_code段的定義,其他都是默認鏈接腳本就已有的內容。

3.3 編譯腳本

Ubuntu x64下的編譯腳本,支持編譯啟用回收優化和不啟用回收優化的情況,參考如下:

#! /bin/bash -e

CFLAGS="-save-temps=obj -Wall"
LDFLAGS="-Wl,-Map=test.map"

CFLAGS_GC="-fdata-sections -ffunction-sections"
LDFLAGS_GC="-Wl,--gc-sections"
LDFLAGS_MAP_GC="-Wl,-Map=test_gc.map"

PRINT_GC="-Wl,--print-gc-sections"

GCC_LDS=default.lds

if [ "$1" = "clean" ]; then
    rm -rf test* *.i *.s *.o *.map
    echo "Clean build done !"
    exit 0
elif [ "$1" = "gc" ]; then
    echo "gcc compile with gc ..."
    single_c_file=`ls *.c | cut -d . -f 1`
    cmd1="gcc -o $single_c_file.o -c *.c $CFLAGS $CFLAGS_GC"
    cmd2="gcc -o test_gc $single_c_file.o $LDFLAGS_GC $LDFLAGS_MAP_GC -T $GCC_LDS $PRINT_GC"
    echo "$cmd1 && $cmd2" && $cmd1 && $cmd2
else
    echo "gcc compile without gc ... (default)"
    cmd="gcc *.c $CFLAGS $LDFLAGS -o test"
    echo $cmd && $cmd
fi

exit 0

3.4 驗證測試

3.4.1 驗證不啟用編譯回收優化的情況

使用./build.sh編譯輸出各種文件,使用grep-rsn unused_func驗證:

**grep -rsnw unused_func1**
main.c:9:void unused_func1(int a)
**Binary file test matches**
test.i:735:void unused_func1(int a)
Binary file test.o matches
test.s:7:       .globl  unused_func1
test.s:8:       .type   unused_func1, @function
test.s:9:unused_func1:
test.s:31:      .size   unused_func1, .-unused_func1
test.map:193:                0x0000000000001169                unused_func1

**grep -rsnw unused_func2**
main.c:14:CODE_KEEP_USED void unused_func2(int a)
**Binary file test matches**
test.i:740:__attribute__((section(".text.keep.used.code"))) void unused_func2(int a)
Binary file test.o matches
test.s:33:      .globl  unused_func2
test.s:34:      .type   unused_func2, @function
test.s:35:unused_func2:
test.s:57:      .size   unused_func2, .-unused_func2
test.map:197:                0x00000000000011b7                unused_func2

我們可以發現,在最后生成的test可執行文件中,都找到了unusedfunc1和testfunc2,也就是說在不啟用回收優化的情況下,跟我們之前的預期是一樣的,這樣增加固件包的尺寸。

3.4.2 驗證啟用編譯回收優化的情況

使用./build.sh gc編譯輸出各種文件,使用grep-rsn unused_func驗證:

**grep -rsnw unused_func1**
Binary file main.o matches
main.c:9:void unused_func1(int a)
test_gc.map:35: .text.unused_func1
main.i:735:void unused_func1(int a)
main.s:6:       .section        .text.unused_func1,"ax",@progbits
main.s:7:       .globl  unused_func1
main.s:8:       .type   unused_func1, @function
main.s:9:unused_func1:
main.s:31:      .size   unused_func1, .-unused_func1

**grep -rsnw unused_func2**
Binary file main.o matches
main.c:14:CODE_KEEP_USED void unused_func2(int a)
test_gc.map:215:                0x0000000000401169                unused_func2
main.i:740:__attribute__((section(".text.keep.used.code"))) void unused_func2(int a)
main.s:33:      .globl  unused_func2
main.s:34:      .type   unused_func2, @function
main.s:35:unused_func2:
main.s:57:      .size   unused_func2, .-unused_func2
**Binary file test_gc matches**

從中,我們發現最后的可執行文件testgc里面只有unusedfunc2,而unused_func1就沒回收了,這個就實現了我們前面定義的需求。

4 原理分析

4.1 實現原理

這里實現的原理主要有4個部分: 第1部分主要修改的是代碼編寫階段,在不希望被回收優化的函數前面添加特殊的段名稱,比如__attribute__((section(".text.keep.used.code"))), 第2部分主要修改的是編譯階段,通過在CFLAGS中添加-fdata-sections-ffunction-sections來實現, 第3部分主要修改的是鏈接階段,通過在LDFLAGS中添加-Wl,-gc-sections來實現, 第4部分主要修改的是鏈接腳本,通過在段名稱中,新增下面的段申明,主要是為了限制指定的段,不被回收。

.text_keep_used_code :
  {
    KEEP (*(.text.keep.used.code))
  }

從原理上說,編譯時使用-fdata-sections-ffunction-sections是為了讓data數據和每一個函數都生成特定的段,以函數xxx為例,那么它將會放在.text.xxx段里面,然后在鏈接階段的時候使用-Wl,-gc-sections回收那些不使用的段。 注意這里回收的最小單位是,所以編譯階段那兩個選項是必不可少的。

4.2 原理驗證分析

4.2.1 確認編譯階段的函數所在的段

這個確認我們可以map文件和.s匯編文件就可以確認,

/* map文件中 unused_fun1的描述 */
35  .text.unused_func1
36                 0x0000000000000000       0x28 main.o

/* 文件中 unused_fun2的描述 */
213  .text.keep.used.code
214                 0x0000000000401169       0x28 main.o
215                 0x0000000000401169                unused_func2

/* 匯編文件中 unused_fun1的描述 */
  6         .section        .text.unused_func1,"ax",@progbits
  7         .globl  unused_func1
  8         .type   unused_func1, @function
  9 unused_func1:
 10 .LFB0:
 11         .cfi_startproc
 12         endbr64
 13         pushq   %rbp
 14         .cfi_def_cfa_offset 16
 15         .cfi_offset 6, -16

/* 匯編文件中 unused_fun2的描述 */
 32         .section        .text.keep.used.code,"ax",@progbits
 33         .globl  unused_func2
 34         .type   unused_func2, @function
 35 unused_func2:
 36 .LFB1:
 37         .cfi_startproc
 38         endbr64
 39         pushq   %rbp
 40         .cfi_def_cfa_offset 16
 41         .cfi_offset 6, -16
 42         movq    %rsp, %rbp
 43         .cfi_def_cfa_register 6

從上面的分析,可以知道函數的段分布是完全符合預期的。

4.2.2 確認鏈接階段的函數所在的段的回收情況

為了驗證這一點,我們可以在LDFLAGS里面添加這一個選項-Wl,--print-gc-sections,這樣我們就可以觀察到鏈接階段最后移除了那些沒有引用的段,從而確認unusedfunc1和unusedfunc2是否被回收。 輸出的關鍵log如下:

 ./build.sh gc
gcc compile with gc ...
gcc -o main.o -c *.c -save-temps=obj -Wall -fdata-sections -ffunction-sections && gcc -o test_gc main.o -Wl,--gc-sections -Wl,-Map=test_gc.map -T default.lds -Wl,--print-gc-sections
/usr/bin/ld: removing unused section '.rodata.cst4' in file '/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o'
/usr/bin/ld: removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o'
/usr/bin/ld: removing unused section '.text.unused_func1' in file 'main.o'

log很明顯就告訴我們,unused section '.text.unused_func1'被回收移除了,而.text.keep.used.code是沒有被回收的,這切好證明了我們的猜想。

5 經驗總結

  • 使用-gc-sections可以回收不使用的代碼段,從而減少代碼尺寸,降低固件占用FLASH的存儲空間;
  • 在特定場景下,修改鏈接腳本可以實現某個函數不被鏈接優化,達到特定的目的。

6 更多分享

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

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

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

【CSDN主頁:架構師李肯】

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

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

【GCC專欄】

信息安全專欄】

【RT-Thread開發筆記】

freeRTOS開發筆記】

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

審核編輯:湯梓紅

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

    關注

    6043

    文章

    44621

    瀏覽量

    638625
  • GCC
    GCC
    +關注

    關注

    0

    文章

    108

    瀏覽量

    24890
  • 函數
    +關注

    關注

    3

    文章

    4346

    瀏覽量

    62979
收藏 人收藏

    評論

    相關推薦

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

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

    使用gcc編譯優化優化問題

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

    gcc編譯中斷函數的方法

    在x86中,一般函數通過"call"指令調用,"ret"指令返回,但是中斷函數不同,它在中斷或者異常發生時自動切入(或者使用"int"指令
    發表于 12-09 06:20

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

    編譯優化系列】使用GCC如何把C文件編譯成可執行文件【GCC
    發表于 07-26 14:56

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

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

    高效的C編程之函數調用

    14.9 函數調用 函數設計的基本原則是使其函數體盡量的小。這樣編譯器可以對函數做更多的
    發表于 10-17 16:49 ?6次下載
    高效的C編程之<b class='flag-5'>函數</b><b class='flag-5'>調用</b>

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

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

    GCC編譯優化指南

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

    編譯優化函數的影響

    編譯器如gcc,可以指定不同的優化參數,在某些條件下,有些函數可能會被優化掉。
    的頭像 發表于 06-22 14:58 ?2891次閱讀
    <b class='flag-5'>編譯</b>器<b class='flag-5'>優化</b>對<b class='flag-5'>函數</b>的影響

    如何讓gcc編譯中斷函數

    在x86中,一般函數通過"call"指令調用,"ret"指令返回,但是中斷函數不同,它在中斷或者異常發生時自動切入(或者使用"int"指令
    發表于 11-26 11:06 ?7次下載
    如何讓<b class='flag-5'>gcc</b><b class='flag-5'>編譯</b>中斷<b class='flag-5'>函數</b>

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

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

    Linux使用gcc編譯程序的語法

    01. 調試相關的宏 在Linux使用gcc編譯程序的時候,對于調試的語句還具有一些特殊的語法。 gcc編譯的過程中,會生成一些宏,可以使用這些宏分別打印當前源文件的信息,主要內容是當
    的頭像 發表于 06-22 10:51 ?758次閱讀
    百家乐官网道具扫描| 安远县| 自治县| sz新全讯网网址112| 百家乐网站可信吗| 开16个赌场敛财| 真人百家乐官网的玩法技巧和规则 | 百家乐庄9点| 百家乐官网筹码桌布| 雅安市| 百家乐官网平台哪个好本站所有数据都是网友推荐及提供 | 哈尔滨百家乐赌场| 赌场百家乐官网实战| 澳门百家乐官网娱乐场| 大发888娱乐城官方网站| 百家乐官网游戏群号| 全讯网程序| 巴彦淖尔市| 百家乐官网追注法| 香港六合彩全年资料| 威尼斯人娱乐骰宝| 威尼斯人娱乐城投注网| 鹤乡棋牌乐| 大发888游戏好吗| 去澳门百家乐的玩法技巧和规则| 百家乐开户优惠多的平台是哪家| 金榜百家乐官网娱乐城| 百家乐官网视频小游戏| 百胜百家乐官网软件| 正蓝旗| 百家乐官网最新投注法| 免费百家乐官网过滤工具| 棋牌百家乐有稳赚的方法吗| 百家乐智能软件| 七胜百家乐赌场娱乐网规则| 360博彩通| 百家乐官网单跳投注法| 博彩网百家乐官网全讯网| 乐享百家乐的玩法技巧和规则| 盈江县| r百家乐娱乐下载|