0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

編譯器將.c文件編譯為.o文件鏈接的過程

技術(shù)讓夢想更偉大 ? 來源:技術(shù)讓夢想更偉大 ? 作者:技術(shù)讓夢想更偉大 ? 2022-10-13 09:36 ? 次閱讀

對大多數(shù)童鞋來說理解編譯器將.c文件編譯為.o文件并不大困難,但是卻難以明白最后鏈接的過程是什么作用和為什么要這樣做? 還有就是我們在樣例工程中啟動的文件為什么是自己編寫的,它又怎樣做到將程序入口引導(dǎo)到main函數(shù)上,那么在這篇中我們就來深入的討論下這兩個話題。

鏈接器

鏈接的過程

首先,想要明白鏈接器的工作原理我們還是要來深入的看看整個編譯過程中具體的方式和原理。 我想大家都知道高級語言出現(xiàn)之前我們所用的匯編語言是除機(jī)器碼外最接近硬件的語言。使用匯編的代碼甚至可以很容易的手動轉(zhuǎn)換為機(jī)器代碼。那么接下來的介紹就需要童鞋們多少了解一點(diǎn)匯編程序了(如8051的匯編)。 在單片機(jī)執(zhí)行的過程中命令被執(zhí)行的順序只有兩種:順序執(zhí)行和根據(jù)指令跳轉(zhuǎn)執(zhí)行位置。在匯編的代碼中,良好的寫法是把各個函數(shù)分塊放在儲存的不同位置上,并在前面寫上程序的標(biāo)號 (如:“START:”),最后由編譯器將START程序處的地址裝入寫有 START標(biāo)號跳轉(zhuǎn)指令的地方。 由此,我們就可以理解C語言被編譯為二進(jìn)制執(zhí)行文件的過程了,首先每個C文件都被編譯為了.o的,帶有未解析地址的中間文件,而后工具鏈的鏈接器將所有C文件的.o文件鏈接將他們有序的排列到儲存中,并將他們個個函數(shù)處的地址解析使得其他不同地方的函數(shù)能夠跳轉(zhuǎn)到該函數(shù)的入口地址,由此一個有序排列的可被單片機(jī)執(zhí)行的文件便生成了。 至于其中各個.c文件產(chǎn)生的功能在單片機(jī)儲存中的排列順序和地址位置,在最后我們鏈接器工作產(chǎn)生的.map文件中是有顯示的,如下面從樣例工程中.map文件中復(fù)制的片段:

																	
																		.isr_vector0x080000000x134 0x08000000.=ALIGN(0x4) *(.isr_vector) .isr_vector0x080000000x134./USER/CoIDE_startup.o 0x08000000g_pfnVectors 0x08000134.=ALIGN(0x4) .text0x080001340x1464 0x08000134.=ALIGN(0x4) *(.text) .text0x080001340x5c/home/yangliu/Library/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/armv7-m/crtbegin.o .text0x080001900x80./USER/main.o 0x08000190main .text0x080002100x68./USER/CoIDE_startup.o 0x08000210Reset_Handler 0x08000210Default_Reset_Handler 0x08000268EXTI2_IRQHandler 0x08000268TIM8_TRG_COM_IRQHandler 0x08000268TIM8_CC_IRQHandler 0x08000268TIM1_CC_IRQHandler 0x08000268TIM6_IRQHandler 0x08000268PVD_IRQHandler 0x08000268SDIO_IRQHandler 0x08000268EXTI3_IRQHandler 0x08000268EXTI0_IRQHandler 0x08000268I2C2_EV_IRQHandler 0x08000268ADC1_2_IRQHandler123456789101112131415161718192021222324252627
																		所以我們的gcc鏈接器就是用來做這個工作的,當(dāng)然不只是gcc的鏈接器,世上所有c程序的編譯工具鏈應(yīng)該都是以這種理念設(shè)計的。。當(dāng)然不排除我見識少,沒見過特殊的。

工具鏈中鏈接器的用法

在實(shí)際中,鏈接器的執(zhí)行程序?qū)嶋H上是arm-none-eabi-ld這個文件,但是我再實(shí)際的編寫過程中在遇到.c和.cpp文件混合的工程中,ld會在鏈接過程中報錯。而對此官方的說明是推薦使用arm-none-eabi-gcc指令來鏈接工程,它會自動的調(diào)用ld程序且不會出現(xiàn)上面這種情況,所以接下來我們都是以arm-none-eabi-gcc指令來介紹鏈接器工作的。

																	
																		$(CC)$(C_OBJ)-Tstm32_f103ze_gcc.ld-o$(TARGET).elf-mthumb-mcpu=cortex-m3-Wl,--start-group-lc-lm-Wl,--end-group-specs=nano.specs-specs=nosys.specs-static-Wl,-cref,-u,Reset_Handler-Wl,-Map=Project.map-Wl,--gc-sections-Wl,--defsym=malloc_getpagesize_P=0x80 1
																		在上面這段截取自樣例工程makefile的代碼片中,我們可以看到在最后生成.elf文件時的指令。變量CCarm-none-eabi-gcc,變量OBJ為所有.o文件。**-o xx.elf**為鏈接.o文件生成.elf文件。

ld文件

在鏈接的過過程中與編譯過程相比其中顯著的與編譯指令不同的便是 -T xx.ld。 在這里 -T xx.ld實(shí)際上是調(diào)用了一個.ld的文件,那么.ld文件是做什么的呢? 這里就比較高深了,在51單片機(jī)中我們知道最后在生成代碼后51單片機(jī)內(nèi)存中會有如 code、xdata、data的區(qū)段,來講代碼中執(zhí)行部分、變量部分等分區(qū)塊放置,而.ld就是一種鏈接器使用的規(guī)則性文件,他告訴鏈接器單片機(jī)系統(tǒng)的ROM、RAM的地址和他們的大小等信息,并指示鏈接器將什么代碼保存在什么位置。 對于.ld文件它是有一套自己的語法及設(shè)置參數(shù)的規(guī)則的,大家可以不具體作了解,但求看懂其中一部分的信息。

																	/*EntryPoint*/ ENTRY(Reset_Handler) /*Highestaddressoftheusermodestack*/ _estack=0x20010000;/*endof64KRAM*/ /*Generatealinkerrorifheapandstackdon'tfitintoRAM*/ _Min_Heap_Size=0;/*requiredamountofheap*/ _Min_Stack_Size=0x200;/*requiredamountofstack*/ /*Specifythememoryareas*/ MEMORY { FLASH(rx):ORIGIN=0x08000000,LENGTH=512K RAM(xrw):ORIGIN=0x20000000,LENGTH=64K MEMORY_B1(rx):ORIGIN=0x60000000,LENGTH=0K } SECTIONS { /*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH /*TheprogramcodeandotherdatagoesintoFLASH*/ .text: { .=ALIGN(4); *(.text)/*.textsections(code)*/ *(.text*)/*.text*sections(code)*/ *(.glue_7)/*gluearmtothumbcode*/ *(.glue_7t)/*gluethumbtoarmcode*/ *(.eh_frame) KEEP(*(.init)) KEEP(*(.fini)) .=ALIGN(4); _etext=.;/*defineaglobalsymbolsatendofcode*/ }>FLASH /*ConstantdatagoesintoFLASH*/ .rodata: { .=ALIGN(4); *(.rodata)/*.rodatasections(constants,strings,etc.)*/ *(.rodata*)/*.rodata*sections(constants,strings,etc.)*/ .=ALIGN(4); }>FLASH .ARM.extab:{*(.ARM.extab*.gnu.linkonce.armextab.*)}>FLASH .ARM:{ __exidx_start=.; *(.ARM.exidx*) __exidx_end=.; }>FLASH .preinit_array: { PROVIDE_HIDDEN(__preinit_array_start=.); KEEP(*(.preinit_array*)) PROVIDE_HIDDEN(__preinit_array_end=.); }>FLASH .init_array: { PROVIDE_HIDDEN(__init_array_start=.); KEEP(*(SORT(.init_array.*))) KEEP(*(.init_array*)) PROVIDE_HIDDEN(__init_array_end=.); }>FLASH .fini_array: { PROVIDE_HIDDEN(__fini_array_start=.); KEEP(*(SORT(.fini_array.*))) KEEP(*(.fini_array*)) PROVIDE_HIDDEN(__fini_array_end=.); }>FLASH /*usedbythestartuptoinitializedata*/ _sidata=LOADADDR(.data); /*InitializeddatasectionsgoesintoRAM,loadLMAcopyaftercode*/ .data: { .=ALIGN(4); _sdata=.;/*createaglobalsymbolatdatastart*/ *(.data)/*.datasections*/ *(.data*)/*.data*sections*/ .=ALIGN(4); _edata=.;/*defineaglobalsymbolatdataend*/ }>RAMAT>FLASH /*Uninitializeddatasection*/ .=ALIGN(4); .bss: { /*Thisisusedbythestartupinordertoinitializethe.bsssecion*/ _sbss=.;/*defineaglobalsymbolatbssstart*/ __bss_start__=_sbss; *(.bss) *(.bss*) *(COMMON) .=ALIGN(4); _ebss=.;/*defineaglobalsymbolatbssend*/ __bss_end__=_ebss; }>RAM /*User_heap_stacksection,usedtocheckthatthereisenoughRAMleft*/ ._user_heap_stack: { .=ALIGN(4); PROVIDE(end=.); PROVIDE(_end=.); .=.+_Min_Heap_Size; .=.+_Min_Stack_Size; .=ALIGN(4); }>RAM /*MEMORY_bank1section,codemustbelocatedhereexplicitly*/ /*Example:externintfoo(void)__attribute__((section(".mb1text")));*/ .memory_b1_text: { *(.mb1text)/*.mb1textsections(code)*/ *(.mb1text*)/*.mb1text*sections(code)*/ *(.mb1rodata)/*read-onlydata(constants)*/ *(.mb1rodata*) }>MEMORY_B1 /*Removeinformationfromthestandardlibraries*/ /DISCARD/: { libc.a(*) libm.a(*) libgcc.a(*) } .ARM.attributes0:{*(.ARM.attributes)} }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
																		至于鏈接時其他的鏈接參數(shù)大部分和編譯參數(shù)相同,不同的也就是:

																	
																		--start-group-lc-lm-Wl,--end-group-specs=nano.specs-specs=nosys.specs-static-Wl,-cref,-u,Reset_Handler-Wl,-Map=Project.map-Wl,--gc-sections-Wl,--defsym=malloc_getpagesize_P=0x80 1
																		對于這些指令我只是大致的清楚是什么,但具體的一些參數(shù)我也不大了解,如果大家有興趣可以自己檢索一下,或者最好的辦法就是到工具鏈中的說明文檔尋找說明。
																		在我們實(shí)際的工程建立及編寫中,我們使用的都是從別處找來的ld文件,在樣例工程中的.ld文件只要在內(nèi)存大小堆棧等位置上根據(jù)stm32具體的型號稍作修改就可以使用了。
																		或者在之后我們介紹libopencm3的驅(qū)動庫中,其作者就有寫好的所有芯片型號的ld文件,我們也可以從那里復(fù)制并修改以用于我們自己的工程。其中l(wèi)d文件中一些變量如堆棧大小等我們會在講解啟動文件的過程中來解析,因?yàn)閱游募蚻d文件中的東西息息相關(guān)。

啟動文件

很多剛接觸stm32不久的童鞋對stm32的啟動文件的印象大多就是教程里的一句話:啟動文件就是stm32在執(zhí)行main函數(shù)前將系統(tǒng)初始化并把PC(即程序計數(shù)器,也就是當(dāng)前執(zhí)行代碼位置的指針)設(shè)置到main函數(shù)的文件。確實(shí)在KEIL或IAR之類的集成開發(fā)環(huán)境中我們不必關(guān)心啟動文件的存在,但是在我們的gcc的使用中,我們就需要去理解這個文件了。 在樣例工程中,我放置的是一個從CooCox開源集成開發(fā)環(huán)境中拷貝修改的啟動文件,在USER目錄下的CoIDE_startup.c,這里我就不放文件的內(nèi)容了,我們只去其中一部分來講。 想要理解啟動代碼,首先我們需要看看GNU編譯器的與其他編譯器不同的新特性之一:*_attribute*((xxx)),在gcc中attribute關(guān)鍵詞用于為函數(shù)或變量等賦予特性,就像MDK中的weak 說明符類似,只不過attribute的使用更具多樣性且靈活。 其次我們要知道,在我們使用的Cortex-M3內(nèi)核中,程序執(zhí)行的最開始會從ROM首地址的第一位取出MSP的數(shù)值(即棧頂?shù)刂分羔?a href="http://ttokpm.com/tags/寄存器/" target="_blank">寄存器),然后會在第二位取出復(fù)位中斷函數(shù)的地址,并跳轉(zhuǎn)過去。且在一般來說,單片機(jī)系統(tǒng)的所有中斷向量表初始時會放在ROM的最前段,所以我們定義了一個函數(shù)指針數(shù)組在堆棧初始值的后方,構(gòu)成了這樣一個被裝入ROM首段地址的數(shù)據(jù):

																	
																		__attribute__((used,section(".isr_vector"))) void(*constg_pfnVectors[])(void)= { /*----------CoreExceptions-------------------------------------------------*/ (void*)&pulStack[STACK_SIZE],/*! Reset_Handler,/*! NMI_Handler,/*! HardFault_Handler,/*! MemManage_Handler,/*! BusFault_Handler,/*! UsageFault_Handler,/*! 0,0,0,0,/*! SVC_Handler,/*! DebugMon_Handler,/*! 0,/*! PendSV_Handler,/*! SysTick_Handler,/*! /*----------ExternalExceptions---------------------------------------------*/ WWDG_IRQHandler,/*! PVD_IRQHandler,/*! TAMPER_IRQHandler,/*! RTC_IRQHandler,/*! FLASH_IRQHandler,/*! RCC_IRQHandler,/*! EXTI0_IRQHandler,/*! EXTI1_IRQHandler,/*! EXTI2_IRQHandler,/*! EXTI3_IRQHandler,/*! EXTI4_IRQHandler,/*! DMA1_Channel1_IRQHandler,/*! DMA1_Channel2_IRQHandler,/*! DMA1_Channel3_IRQHandler,/*! DMA1_Channel4_IRQHandler,/*! DMA1_Channel5_IRQHandler,/*! DMA1_Channel6_IRQHandler,/*! DMA1_Channel7_IRQHandler,/*! ADC1_2_IRQHandler,/*! USB_HP_CAN1_TX_IRQHandler,/*! USB_LP_CAN1_RX0_IRQHandler,/*! CAN1_RX1_IRQHandler,/*! CAN1_SCE_IRQHandler,/*! EXTI9_5_IRQHandler,/*! TIM1_BRK_IRQHandler,/*! TIM1_UP_IRQHandler,/*! TIM1_TRG_COM_IRQHandler,/*! TIM1_CC_IRQHandler,/*! TIM2_IRQHandler,/*! TIM3_IRQHandler,/*! TIM4_IRQHandler,/*! I2C1_EV_IRQHandler,/*! I2C1_ER_IRQHandler,/*! I2C2_EV_IRQHandler,/*! I2C2_ER_IRQHandler,/*! SPI1_IRQHandler,/*! SPI2_IRQHandler,/*! USART1_IRQHandler,/*! USART2_IRQHandler,/*! USART3_IRQHandler,/*! EXTI15_10_IRQHandler,/*! RTCAlarm_IRQHandler,/*! USBWakeUp_IRQHandler,/*! TIM8_BRK_IRQHandler,/*! TIM8_UP_IRQHandler,/*! TIM8_TRG_COM_IRQHandler,/*! TIM8_CC_IRQHandler,/*! ADC3_IRQHandler,/*! FSMC_IRQHandler,/*! SDIO_IRQHandler,/*! TIM5_IRQHandler,/*! SPI3_IRQHandler,/*! UART4_IRQHandler,/*! UART5_IRQHandler,/*! TIM6_IRQHandler,/*! TIM7_IRQHandler,/*! DMA2_Channel1_IRQHandler,/*! DMA2_Channel2_IRQHandler,/*! DMA2_Channel3_IRQHandler,/*! DMA2_Channel4_5_IRQHandler,/*! (void*)0xF108F85F/*! };
																		注意在數(shù)組的attribute的修飾中,它將函數(shù)的位置規(guī)定在了[section(“.isr_vector”)]的位置,而[.isr_vector]則在ld文件中定義在FLASH開始的地方:

																	
																		/*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH
																		所以顯而易見的,在啟動后第二個周期里內(nèi)核讀取了復(fù)位向量表的地址并跳轉(zhuǎn)了過去,所以單片機(jī)的啟動代碼必然存放于rest vector中,我們在啟動文件中找到復(fù)位函數(shù):

																	#pragmaweakReset_Handler=Default_Reset_Handler voidDefault_Reset_Handler(void) { /*Initializedataandbss*/ unsignedlong*pulSrc,*pulDest; /*CopythedatasegmentinitializersfromflashtoSRAM*/ pulSrc=&_sidata; for(pulDest=&_sdata;pulDest/*Zerofillthebsssegment.Thisisdonewithinlineassemblysincethis willclearthevalueofpulDestifitisnotkeptinaregister.*/
__asm("ldrr0,=_sbss " "ldrr1,=_ebss " "movr2,#0 " ".thumb_func " "zero_loop: " "cmpr0,r1 " "itlt " "strltr2,[r0],#4 " "bltzero_loop"); /*Setupthemicrocontrollersystem.*/ SystemInit(); /*Calltheapplication'sentrypoint.*/ main(); } 在啟動函數(shù)中我們可以清晰地看到,在最后一步中,單片機(jī)的程序被轉(zhuǎn)入到了main函數(shù)的入口,那么在執(zhí)行main函數(shù)之前,C語言,和內(nèi)聯(lián)匯編程序干了什么呢?首先頭位置的C語言將終端向量表從ROM頭位置,復(fù)制到了RAM頭位置(即:0x20000000),這里在RAM中的終端向量表時間上沒有沒我們用到,當(dāng)然這是因?yàn)樵贛3的內(nèi)核中,它允許用戶在NIVC的寄存器中重新定義終端向量表的位置,我們可以使用

																	
																		NVIC_SetVectorTable(NVIC_VectTab_FLASH,0);
																		這個函數(shù)來將終端向量表設(shè)置到到0x20000000位置。該功能實(shí)際上是用于方便裝有系統(tǒng)的環(huán)境中使用,可以加快終端響應(yīng)的速度,同時可以快速的動態(tài)的更改終端處理的程序。
																		當(dāng)然在我們的應(yīng)用中并未使用到這一特性,所以此處的復(fù)制中斷向量表的操作是可以刪除的,它在此的作用只是為了防止用戶在程序中使用了重定向向量表語句而使得程序跑飛所添加的。因?yàn)榻K端向量是系統(tǒng)最基礎(chǔ)穩(wěn)定性的保證,如果在硬件錯誤發(fā)生等中斷發(fā)生的情況下單片機(jī)無法正確的跳轉(zhuǎn),會對代碼調(diào)試和系統(tǒng)穩(wěn)定運(yùn)行帶來嚴(yán)重的影響。
																		之后緊跟的這幾條匯編代碼實(shí)現(xiàn)的是:全局變量與靜態(tài)變量的初始化并將其從flash中調(diào)入內(nèi)存,即在C語言運(yùn)行全局變量與靜態(tài)變量的初始化操作。在此之后, SystemInit();函數(shù)被調(diào)用,配置好時鐘等參數(shù)。最后我們的main函數(shù)就可以執(zhí)行啦~。
																		這便是是我們在這個例程中使用的啟動文件,而在keil工程中,這個文件是用匯編代碼寫成的,但這些文件功能都是一樣的,設(shè)置終端向量表,初始化全局與靜態(tài)變量,進(jìn)入main函數(shù),都是這樣的流程。
																		在gcc的環(huán)境中我們也可以是用匯編編寫這樣的文件,我們面前的選擇有很多,當(dāng)然我們沒必要自己編寫這些鏈接文件和啟動代碼,在之后的實(shí)際的工程建立中我會告訴大家實(shí)際的方法。不過在此之前我們還是要先把基礎(chǔ)的內(nèi)容學(xué)好再說。

其他的說明

在文件中我們看到了**_sidata、_sdata**等變量,這些變量在文件的前面部分被定義為外部:

																	externunsignedlong_sidata;/*! externunsignedlong_sdata;/*! externunsignedlong_edata;/*! externunsignedlong_sbss;/*! externunsignedlong_ebss;/*!
																		而該文件卻并未包含任何.h文件,那么他們從哪來的呢?細(xì)心的同學(xué)可能已經(jīng)注意到了,我們之前提到過,這些變量的定義實(shí)際上都來自于ld文件中,他們在ld文件中被定義,最后鏈接器會將他們轉(zhuǎn)換為實(shí)際的地址給我們的程序所使用的。
																		最后再說一下 attribute ((weak))屬性,該屬性表面其后的變量或是函數(shù)為弱申明,即在沒有其他申明情況下調(diào)用改函數(shù),而如果其他地方申明了,則會頂替該函數(shù)。所以在啟動文件中,他們被用來修飾中斷處理函為中斷向量表提供一個默認(rèn)的地址,而當(dāng)用戶定義后,就將地址轉(zhuǎn)為用戶定義的位置。

總結(jié)

說了這么多,這也是我們在這個系列中比較難以理解的部分,因?yàn)樯婕暗搅薌NU C的特性和計算機(jī)編譯鏈接的最基礎(chǔ)的部分,還有Cortex-M3內(nèi)核工作的方式,但是請大家仔細(xì)的去理解學(xué)習(xí),如果看了這篇文章還不懂那就多查查相關(guān)的資料,當(dāng)你理解并貫通 這些知識時,你會發(fā)現(xiàn)原來在單片機(jī)上c語言是這樣工作的,原來中斷系統(tǒng)是這么的重要,你會發(fā)現(xiàn)單片機(jī)在你的眼前是如此的透徹。 在最后,我們還要說說,其實(shí)很多同學(xué)目前掌握的都是一個很簡單的單片機(jī)應(yīng)用方式,這都是被keil、IAR之流慣壞的,實(shí)際上在單片機(jī)背后,其實(shí)際的工作復(fù)雜而又充滿著精致的設(shè)計,這點(diǎn)我們會在之后的nuttx系統(tǒng)使用中見到。 那時你會發(fā)現(xiàn)原來我們使用的M3單片機(jī)還有這么多的我們之前沒用過的中斷,原來m3的內(nèi)核如此強(qiáng)大。對此我推薦大家還是學(xué)一遍51單片機(jī)的匯編教程,當(dāng)你理解和使用過匯編后,你會更容易理解未來的講解內(nèi)容,同時也更容易理解此篇的內(nèi)容。當(dāng)然如果大家有興趣可以先自己看看由宋巖前輩翻譯的Cortex-M3 權(quán)威指南,來提前感受一下Cortex-M3內(nèi)核的魅力。

審核編輯:湯梓紅


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 單片機(jī)
    +關(guān)注

    關(guān)注

    6030

    文章

    44489

    瀏覽量

    631960
  • STM32
    +關(guān)注

    關(guān)注

    2264

    文章

    10854

    瀏覽量

    354288
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1617

    瀏覽量

    49015

原文標(biāo)題:STM32高級開發(fā)——鏈接器與啟動文件

文章出處:【微信號:技術(shù)讓夢想更偉大,微信公眾號:技術(shù)讓夢想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    C語言的編譯鏈接過程

    ? C語言的編譯鏈接過程要把我們編寫的一個C程序源代碼轉(zhuǎn)換成可以在硬件上運(yùn)行的程序(可執(zhí)行代碼),需要進(jìn)行編譯
    的頭像 發(fā)表于 08-21 10:06 ?2474次閱讀
    <b class='flag-5'>C</b>語言的<b class='flag-5'>編譯</b><b class='flag-5'>鏈接過程</b>

    常用編輯之GCC編譯器

    ,輸出結(jié)果是一樣的。elf@ubuntu:~/work/example/hello$ gcc hello.c4、GCC編譯過程GCC編譯器編譯
    發(fā)表于 08-24 11:05

    matlab的m文件編譯為dll文件

    ');fprintf(fid,'%f',y);fclose('all');使用過mcc命令,但不能成功編譯,是不是mcc編譯器有局限性?對這樣的m文件該用什么方法進(jìn)行編譯?求高手指導(dǎo)!
    發(fā)表于 05-18 21:21

    gcc 編譯器編譯過程詳解

    表示用gcc來編譯源程序,-o 選項表示要求編譯器輸出的可執(zhí)行文件名為hello,而hello.c是源程序
    發(fā)表于 07-03 09:51

    gcc編譯器編譯過程介紹

    表示用gcc來編譯源程序,-o 選項表示要求編譯器輸出的可執(zhí)行文件名為hello,而hello.c是源程序
    發(fā)表于 07-09 07:49

    C語言編譯過程是怎樣的

    -o xx.s匯編階段生成目標(biāo)代碼:gcc -C xx.s -o xx.ogcc -C xx.c -o
    發(fā)表于 10-27 09:00

    EMC單片機(jī)C編譯器

    EMC單片機(jī)C編譯器   簡單講,編譯器就是“高級語言”翻譯為“機(jī)器語言(低級語言)”的程序?! 「呒売嬎銠C(jī)語言便于人編寫,閱
    發(fā)表于 03-29 14:23 ?68次下載

    Linux下C/C++編譯器gcc使用指南

    test.c:如果沒有指定輸出的文件,默認(rèn)編譯出一個名為a.out的程序 gcc test.c -o
    發(fā)表于 11-02 10:59 ?0次下載

    使用編譯器預(yù)處理文件編譯的命令是什么?

    如果你使用的是集成開發(fā)環(huán)境,那么你點(diǎn)擊編譯按鈕就可生成可執(zhí)行文件,然后點(diǎn)擊運(yùn)行即可運(yùn)行。那么,你知道從源代碼到可執(zhí)行文件經(jīng)歷了哪些過程嗎。僅僅是編譯
    的頭像 發(fā)表于 06-24 11:49 ?3032次閱讀

    ASM源文件編譯器軟件免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是ASM源文件編譯器軟件免費(fèi)下載。適用于32位計算機(jī),asm編譯器ASM51.exe放在同一目錄,在dos狀態(tài)編譯
    發(fā)表于 08-07 08:00 ?5次下載
    ASM源<b class='flag-5'>文件</b><b class='flag-5'>編譯器</b>軟件免費(fèi)下載

    華為方舟編譯器使用指南

    當(dāng)前方舟編譯器支持 Java/Kotlin 程序字節(jié)碼的前端輸入,其它編程語言的支持(如 C/C++/JS 等)還在規(guī)劃中,方舟編譯器的中間表示(IR)轉(zhuǎn)換
    發(fā)表于 10-14 14:56 ?1次下載
    華為方舟<b class='flag-5'>編譯器</b>使用指南

    解析C語言編譯過程中所做的工作

    是有幫助的。而且清楚的了解編譯鏈接過程還對我們在編程時定位錯誤,以及編程時盡量調(diào)動編譯器的檢測錯誤會有很大的幫助的。 編譯 編譯是讀取源程序
    的頭像 發(fā)表于 06-27 10:21 ?3108次閱讀
    解析<b class='flag-5'>C</b>語言<b class='flag-5'>編譯</b><b class='flag-5'>過程</b>中所做的工作

    Verilog HDL 編譯器指令說明

    編譯時,特定的編譯器指令在整個編譯過程中有效(編譯過程可跨越多個
    的頭像 發(fā)表于 11-03 09:31 ?3580次閱讀
    Verilog HDL <b class='flag-5'>編譯器</b>指令說明

    淺談hightec的編譯鏈接文件

    hightec的編譯鏈接文件的后綴為ld,因此后文簡稱ld文件,ld文件主要分為三個部分:宏定義、MEMORY命令、SECTIONS命令。
    的頭像 發(fā)表于 03-15 11:13 ?3778次閱讀

    SDCC-Linux下的51 MCU編譯器

    SDCC (小型設(shè)備C編譯器)是為8位微控制開發(fā)的免費(fèi)C編譯器。盡管兼容多種不同體系結(jié)構(gòu),但SDCC
    的頭像 發(fā)表于 06-16 15:19 ?4833次閱讀