RM新时代网站-首页

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

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

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

編譯器將.c文件編譯為.o文件鏈接的過(guò)程

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

對(duì)大多數(shù)童鞋來(lái)說(shuō)理解編譯器將.c文件編譯為.o文件并不大困難,但是卻難以明白最后鏈接的過(guò)程是什么作用和為什么要這樣做? 還有就是我們?cè)跇永こ讨袉?dòng)的文件為什么是自己編寫(xiě)的,它又怎樣做到將程序入口引導(dǎo)到main函數(shù)上,那么在這篇中我們就來(lái)深入的討論下這兩個(gè)話題。

鏈接器

鏈接的過(guò)程

首先,想要明白鏈接器的工作原理我們還是要來(lái)深入的看看整個(gè)編譯過(guò)程中具體的方式和原理。 我想大家都知道高級(jí)語(yǔ)言出現(xiàn)之前我們所用的匯編語(yǔ)言是除機(jī)器碼外最接近硬件的語(yǔ)言。使用匯編的代碼甚至可以很容易的手動(dòng)轉(zhuǎn)換為機(jī)器代碼。那么接下來(lái)的介紹就需要童鞋們多少了解一點(diǎn)匯編程序了(如8051的匯編)。 在單片機(jī)執(zhí)行的過(guò)程中命令被執(zhí)行的順序只有兩種:順序執(zhí)行和根據(jù)指令跳轉(zhuǎn)執(zhí)行位置。在匯編的代碼中,良好的寫(xiě)法是把各個(gè)函數(shù)分塊放在儲(chǔ)存的不同位置上,并在前面寫(xiě)上程序的標(biāo)號(hào) (如:“START:”),最后由編譯器將START程序處的地址裝入寫(xiě)有 START標(biāo)號(hào)跳轉(zhuǎn)指令的地方。 由此,我們就可以理解C語(yǔ)言被編譯為二進(jìn)制執(zhí)行文件的過(guò)程了,首先每個(gè)C文件都被編譯為了.o的,帶有未解析地址的中間文件,而后工具鏈的鏈接器將所有C文件的.o文件鏈接將他們有序的排列到儲(chǔ)存中,并將他們個(gè)個(gè)函數(shù)處的地址解析使得其他不同地方的函數(shù)能夠跳轉(zhuǎn)到該函數(shù)的入口地址,由此一個(gè)有序排列的可被單片機(jī)執(zhí)行的文件便生成了。 至于其中各個(gè).c文件產(chǎn)生的功能在單片機(jī)儲(chǔ)存中的排列順序和地址位置,在最后我們鏈接器工作產(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鏈接器就是用來(lái)做這個(gè)工作的,當(dāng)然不只是gcc的鏈接器,世上所有c程序的編譯工具鏈應(yīng)該都是以這種理念設(shè)計(jì)的。。當(dāng)然不排除我見(jiàn)識(shí)少,沒(méi)見(jiàn)過(guò)特殊的。

工具鏈中鏈接器的用法

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

																	
																		$(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文件時(shí)的指令。變量CCarm-none-eabi-gcc,變量OBJ為所有.o文件。**-o xx.elf**為鏈接.o文件生成.elf文件。

ld文件

在鏈接的過(guò)過(guò)程中與編譯過(guò)程相比其中顯著的與編譯指令不同的便是 -T xx.ld。 在這里 -T xx.ld實(shí)際上是調(diào)用了一個(gè).ld的文件,那么.ld文件是做什么的呢? 這里就比較高深了,在51單片機(jī)中我們知道最后在生成代碼后51單片機(jī)內(nèi)存中會(huì)有如 code、xdata、data的區(qū)段,來(lái)講代碼中執(zhí)行部分、變量部分等分區(qū)塊放置,而.ld就是一種鏈接器使用的規(guī)則性文件,他告訴鏈接器單片機(jī)系統(tǒng)的ROM、RAM的地址和他們的大小等信息,并指示鏈接器將什么代碼保存在什么位置。 對(duì)于.ld文件它是有一套自己的語(yǔ)法及設(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ù)大部分和編譯參數(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
																		對(duì)于這些指令我只是大致的清楚是什么,但具體的一些參數(shù)我也不大了解,如果大家有興趣可以自己檢索一下,或者最好的辦法就是到工具鏈中的說(shuō)明文檔尋找說(shuō)明。
																		在我們實(shí)際的工程建立及編寫(xiě)中,我們使用的都是從別處找來(lái)的ld文件,在樣例工程中的.ld文件只要在內(nèi)存大小堆棧等位置上根據(jù)stm32具體的型號(hào)稍作修改就可以使用了。
																		或者在之后我們介紹libopencm3的驅(qū)動(dòng)庫(kù)中,其作者就有寫(xiě)好的所有芯片型號(hào)的ld文件,我們也可以從那里復(fù)制并修改以用于我們自己的工程。其中l(wèi)d文件中一些變量如堆棧大小等我們會(huì)在講解啟動(dòng)文件的過(guò)程中來(lái)解析,因?yàn)閱?dòng)文件和ld文件中的東西息息相關(guān)。

啟動(dòng)文件

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

																	
																		/*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH
																		所以顯而易見(jiàn)的,在啟動(dòng)后第二個(gè)周期里內(nèi)核讀取了復(fù)位向量表的地址并跳轉(zhuǎn)了過(guò)去,所以單片機(jī)的啟動(dòng)代碼必然存放于rest vector中,我們?cè)趩?dòng)文件中找到復(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(); } 在啟動(dòng)函數(shù)中我們可以清晰地看到,在最后一步中,單片機(jī)的程序被轉(zhuǎn)入到了main函數(shù)的入口,那么在執(zhí)行main函數(shù)之前,C語(yǔ)言,和內(nèi)聯(lián)匯編程序干了什么呢?首先頭位置的C語(yǔ)言將終端向量表從ROM頭位置,復(fù)制到了RAM頭位置(即:0x20000000),這里在RAM中的終端向量表時(shí)間上沒(méi)有沒(méi)我們用到,當(dāng)然這是因?yàn)樵贛3的內(nèi)核中,它允許用戶(hù)在NIVC的寄存器中重新定義終端向量表的位置,我們可以使用

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

其他的說(shuō)明

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

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

總結(jié)

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

審核編輯:湯梓紅


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

    關(guān)注

    6035

    文章

    44554

    瀏覽量

    634632
  • STM32
    +關(guān)注

    關(guān)注

    2270

    文章

    10895

    瀏覽量

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

    關(guān)注

    1

    文章

    1623

    瀏覽量

    49108

原文標(biāo)題:STM32高級(jí)開(kāi)發(fā)——鏈接器與啟動(dòng)文件

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

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語(yǔ)言的編譯鏈接過(guò)程

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

    常用編輯之GCC編譯器

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

    matlab的m文件編譯為dll文件

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

    gcc 編譯器編譯過(guò)程詳解

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

    gcc編譯器編譯過(guò)程介紹

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

    C語(yǔ)言編譯過(guò)程是怎樣的

    -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編譯器   簡(jiǎn)單講,編譯器就是“高級(jí)語(yǔ)言”翻譯為“機(jī)器語(yǔ)言(低級(jí)語(yǔ)言)”的程序?! 「呒?jí)計(jì)算機(jī)語(yǔ)言便于人編寫(xiě),閱
    發(fā)表于 03-29 14:23 ?68次下載

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

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

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

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

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

    本文檔的主要內(nèi)容詳細(xì)介紹的是ASM源文件編譯器軟件免費(fèi)下載。適用于32位計(jì)算機(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é)碼的前端輸入,其它編程語(yǔ)言的支持(如 C/C++/JS 等)還在規(guī)劃中,方舟編譯器的中間表示(IR)轉(zhuǎn)換
    發(fā)表于 10-14 14:56 ?1次下載
    華為方舟<b class='flag-5'>編譯器</b>使用指南

    解析C語(yǔ)言編譯過(guò)程中所做的工作

    是有幫助的。而且清楚的了解編譯鏈接過(guò)程還對(duì)我們?cè)诰幊虝r(shí)定位錯(cuò)誤,以及編程時(shí)盡量調(diào)動(dòng)編譯器的檢測(cè)錯(cuò)誤會(huì)有很大的幫助的。 編譯 編譯是讀取源程序
    的頭像 發(fā)表于 06-27 10:21 ?3159次閱讀
    解析<b class='flag-5'>C</b>語(yǔ)言<b class='flag-5'>編譯</b><b class='flag-5'>過(guò)程</b>中所做的工作

    Verilog HDL 編譯器指令說(shuō)明

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

    淺談hightec的編譯鏈接文件

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

    SDCC-Linux下的51 MCU編譯器

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