对大多数童鞋来说理解编译器将.c文件编译为.o文件并不大困难,但是却难以明白最后链接的过程是什么作用和为什么要这样做? 还有就是我们在样例工程中启动的文件为什么是自己编写的,它又怎样做到将程序入口引导到main函数上,那么在这篇中我们就来深入的讨论下这两个话题。
链接器
链接的过程
首先,想要明白链接器的工作原理我们还是要来深入的看看整个编译过程中具体的方式和原理。 我想大家都知道高级语言出现之前我们所用的汇编语言是除机器码外最接近硬件的语言。使用汇编的代码甚至可以很容易的手动转换为机器代码。那么接下来的介绍就需要童鞋们多少了解一点汇编程序了(如8051的汇编)。 在单片机执行的过程中命令被执行的顺序只有两种:顺序执行和根据指令跳转执行位置。在汇编的代码中,良好的写法是把各个函数分块放在储存的不同位置上,并在前面写上程序的标号 (如:“START:”),最后由编译器将START程序处的地址装入写有 START标号跳转指令的地方。 由此,我们就可以理解C语言被编译为二进制执行文件的过程了,首先每个C文件都被编译为了.o的,带有未解析地址的中间文件,而后工具链的链接器将所有C文件的.o文件链接将他们有序的排列到储存中,并将他们个个函数处的地址解析使得其他不同地方的函数能够跳转到该函数的入口地址,由此一个有序排列的可被单片机执行的文件便生成了。 至于其中各个.c文件产生的功能在单片机储存中的排列顺序和地址位置,在最后我们链接器工作产生的.map文件中是有显示的,如下面从样例工程中.map文件中复制的片段:.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链接器就是用来做这个工作的,当然不只是gcc的链接器,世上所有c程序的编译工具链应该都是以这种理念设计的。。当然不排除我见识少,没见过特殊的。
工具链中链接器的用法
在实际中,链接器的执行程序实际上是arm-none-eabi-ld这个文件,但是我再实际的编写过程中在遇到.c和.cpp文件混合的工程中,ld会在链接过程中报错。而对此官方的说明是推荐使用arm-none-eabi-gcc指令来链接工程,它会自动的调用ld程序且不会出现上面这种情况,所以接下来我们都是以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文件时的指令。变量CC为arm-none-eabi-gcc,变量OBJ为所有.o文件。**-o xx.elf**为链接.o文件生成.elf文件。
ld文件
在链接的过过程中与编译过程相比其中显著的与编译指令不同的便是 -T xx.ld。 在这里 -T xx.ld实际上是调用了一个.ld的文件,那么.ld文件是做什么的呢? 这里就比较高深了,在51单片机中我们知道最后在生成代码后51单片机内存中会有如 code、xdata、data的区段,来讲代码中执行部分、变量部分等分区块放置,而.ld就是一种链接器使用的规则性文件,他告诉链接器单片机系统的ROM、RAM的地址和他们的大小等信息,并指示链接器将什么代码保存在什么位置。 对于.ld文件它是有一套自己的语法及设置参数的规则的,大家可以不具体作了解,但求看懂其中一部分的信息。/*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 至于链接时其他的链接参数大部分和编译参数相同,不同的也就是:
--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 对于这些指令我只是大致的清楚是什么,但具体的一些参数我也不大了解,如果大家有兴趣可以自己检索一下,或者最好的办法就是到工具链中的说明文档寻找说明。 在我们实际的工程建立及编写中,我们使用的都是从别处找来的ld文件,在样例工程中的.ld文件只要在内存大小堆栈等位置上根据stm32具体的型号稍作修改就可以使用了。 或者在之后我们介绍libopencm3的驱动库中,其作者就有写好的所有芯片型号的ld文件,我们也可以从那里复制并修改以用于我们自己的工程。其中ld文件中一些变量如堆栈大小等我们会在讲解启动文件的过程中来解析,因为启动文件和ld文件中的东西息息相关。
启动文件
很多刚接触stm32不久的童鞋对stm32的启动文件的印象大多就是教程里的一句话:启动文件就是stm32在执行main函数前将系统初始化并把PC(即程序计数器,也就是当前执行代码位置的指针)设置到main函数的文件。确实在KEIL或IAR之类的集成开发环境中我们不必关心启动文件的存在,但是在我们的gcc的使用中,我们就需要去理解这个文件了。 在样例工程中,我放置的是一个从CooCox开源集成开发环境中拷贝修改的启动文件,在USER目录下的CoIDE_startup.c,这里我就不放文件的内容了,我们只去其中一部分来讲。 想要理解启动代码,首先我们需要看看GNU编译器的与其他编译器不同的新特性之一:*_attribute*((xxx)),在gcc中attribute关键词用于为函数或变量等赋予特性,就像MDK中的weak 说明符类似,只不过attribute的使用更具多样性且灵活。 其次我们要知道,在我们使用的Cortex-M3内核中,程序执行的最开始会从ROM首地址的第一位取出MSP的数值(即栈顶地址指针寄存器),然后会在第二位取出复位中断函数的地址,并跳转过去。且在一般来说,单片机系统的所有中断向量表初始时会放在ROM的最前段,所以我们定义了一个函数指针数组在堆栈初始值的后方,构成了这样一个被装入ROM首段地址的数据:__attribute__((used,section(".isr_vector"))) void(*constg_pfnVectors[])(void)= { /*----------CoreExceptions-------------------------------------------------*/ (void*)&pulStack[STACK_SIZE],/*!< The initial stack pointer */ Reset_Handler,/*!< Reset Handler */ NMI_Handler,/*!< NMI Handler */ HardFault_Handler,/*!< Hard Fault Handler */ MemManage_Handler,/*!< MPU Fault Handler */ BusFault_Handler,/*!< Bus Fault Handler */ UsageFault_Handler,/*!< Usage Fault Handler */ 0,0,0,0,/*!< Reserved */ SVC_Handler,/*!< SVCall Handler */ DebugMon_Handler,/*!< Debug Monitor Handler */ 0,/*!< Reserved */ PendSV_Handler,/*!< PendSV Handler */ SysTick_Handler,/*!< SysTick Handler */ /*----------ExternalExceptions---------------------------------------------*/ WWDG_IRQHandler,/*!< 0: Window Watchdog */ PVD_IRQHandler,/*!< 1: PVD through EXTI Line detect */ TAMPER_IRQHandler,/*!< 2: Tamper */ RTC_IRQHandler,/*!< 3: RTC */ FLASH_IRQHandler,/*!< 4: Flash */ RCC_IRQHandler,/*!< 5: RCC */ EXTI0_IRQHandler,/*!< 6: EXTI Line 0 */ EXTI1_IRQHandler,/*!< 7: EXTI Line 1 */ EXTI2_IRQHandler,/*!< 8: EXTI Line 2 */ EXTI3_IRQHandler,/*!< 9: EXTI Line 3 */ EXTI4_IRQHandler,/*!< 10: EXTI Line 4 */ DMA1_Channel1_IRQHandler,/*!< 11: DMA1 Channel 1 */ DMA1_Channel2_IRQHandler,/*!< 12: DMA1 Channel 2 */ DMA1_Channel3_IRQHandler,/*!< 13: DMA1 Channel 3 */ DMA1_Channel4_IRQHandler,/*!< 14: DMA1 Channel 4 */ DMA1_Channel5_IRQHandler,/*!< 15: DMA1 Channel 5 */ DMA1_Channel6_IRQHandler,/*!< 16: DMA1 Channel 6 */ DMA1_Channel7_IRQHandler,/*!< 17: DMA1 Channel 7 */ ADC1_2_IRQHandler,/*!< 18: ADC1 & ADC2 */ USB_HP_CAN1_TX_IRQHandler,/*!< 19: USB High Priority or CAN1 TX */ USB_LP_CAN1_RX0_IRQHandler,/*!< 20: USB Low Priority or CAN1 RX0 */ CAN1_RX1_IRQHandler,/*!< 21: CAN1 RX1 */ CAN1_SCE_IRQHandler,/*!< 22: CAN1 SCE */ EXTI9_5_IRQHandler,/*!< 23: EXTI Line 9..5 */ TIM1_BRK_IRQHandler,/*!< 24: TIM1 Break */ TIM1_UP_IRQHandler,/*!< 25: TIM1 Update */ TIM1_TRG_COM_IRQHandler,/*!< 26: TIM1 Trigger and Commutation */ TIM1_CC_IRQHandler,/*!< 27: TIM1 Capture Compare */ TIM2_IRQHandler,/*!< 28: TIM2 */ TIM3_IRQHandler,/*!< 29: TIM3 */ TIM4_IRQHandler,/*!< 30: TIM4 */ I2C1_EV_IRQHandler,/*!< 31: I2C1 Event */ I2C1_ER_IRQHandler,/*!< 32: I2C1 Error */ I2C2_EV_IRQHandler,/*!< 33: I2C2 Event */ I2C2_ER_IRQHandler,/*!< 34: I2C2 Error */ SPI1_IRQHandler,/*!< 35: SPI1 */ SPI2_IRQHandler,/*!< 36: SPI2 */ USART1_IRQHandler,/*!< 37: USART1 */ USART2_IRQHandler,/*!< 38: USART2 */ USART3_IRQHandler,/*!< 39: USART3 */ EXTI15_10_IRQHandler,/*!< 40: EXTI Line 15..10 */ RTCAlarm_IRQHandler,/*!< 41: RTC Alarm through EXTI Line */ USBWakeUp_IRQHandler,/*!< 42: USB Wakeup from suspend */ TIM8_BRK_IRQHandler,/*!< 43: TIM8 Break */ TIM8_UP_IRQHandler,/*!< 44: TIM8 Update */ TIM8_TRG_COM_IRQHandler,/*!< 45: TIM8 Trigger and Commutation */ TIM8_CC_IRQHandler,/*!< 46: TIM8 Capture Compare */ ADC3_IRQHandler,/*!< 47: ADC3 */ FSMC_IRQHandler,/*!< 48: FSMC */ SDIO_IRQHandler,/*!< 49: SDIO */ TIM5_IRQHandler,/*!< 50: TIM5 */ SPI3_IRQHandler,/*!< 51: SPI3 */ UART4_IRQHandler,/*!< 52: UART4 */ UART5_IRQHandler,/*!< 52: UART5 */ TIM6_IRQHandler,/*!< 53: TIM6 */ TIM7_IRQHandler,/*!< 54: TIM7 */ DMA2_Channel1_IRQHandler,/*!< 55: DMA2 Channel1 */ DMA2_Channel2_IRQHandler,/*!< 56: DMA2 Channel2 */ DMA2_Channel3_IRQHandler,/*!< 57: DMA2 Channel3 */ DMA2_Channel4_5_IRQHandler,/*!< 58: DMA2 Channel4 & Channel5 */ (void*)0xF108F85F/*!< Boot in RAM mode */ }; 注意在数组的attribute的修饰中,它将函数的位置规定在了[section(“.isr_vector”)]的位置,而[.isr_vector]则在ld文件中定义在FLASH开始的地方:
/*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH 所以显而易见的,在启动后第二个周期里内核读取了复位向量表的地址并跳转了过去,所以单片机的启动代码必然存放于rest vector中,我们在启动文件中找到复位函数:
#pragmaweakReset_Handler=Default_Reset_Handler voidDefault_Reset_Handler(void) { /*Initializedataandbss*/ unsignedlong*pulSrc,*pulDest; /*CopythedatasegmentinitializersfromflashtoSRAM*/ pulSrc=&_sidata; for(pulDest=&_sdata;pulDest< &_edata; ) { *(pulDest++) = *(pulSrc++); } /*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(); } 在启动函数中我们可以清晰地看到,在最后一步中,单片机的程序被转入到了main函数的入口,那么在执行main函数之前,C语言,和内联汇编程序干了什么呢?首先头位置的C语言将终端向量表从ROM头位置,复制到了RAM头位置(即:0x20000000),这里在RAM中的终端向量表时间上没有没我们用到,当然这是因为在M3的内核中,它允许用户在NIVC的寄存器中重新定义终端向量表的位置,我们可以使用
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0); 这个函数来将终端向量表设置到到0x20000000位置。该功能实际上是用于方便装有系统的环境中使用,可以加快终端响应的速度,同时可以快速的动态的更改终端处理的程序。 当然在我们的应用中并未使用到这一特性,所以此处的复制中断向量表的操作是可以删除的,它在此的作用只是为了防止用户在程序中使用了重定向向量表语句而使得程序跑飞所添加的。因为终端向量是系统最基础稳定性的保证,如果在硬件错误发生等中断发生的情况下单片机无法正确的跳转,会对代码调试和系统稳定运行带来严重的影响。 之后紧跟的这几条汇编代码实现的是:全局变量与静态变量的初始化并将其从flash中调入内存,即在C语言运行全局变量与静态变量的初始化操作。在此之后, SystemInit();函数被调用,配置好时钟等参数。最后我们的main函数就可以执行啦~。 这便是是我们在这个例程中使用的启动文件,而在keil工程中,这个文件是用汇编代码写成的,但这些文件功能都是一样的,设置终端向量表,初始化全局与静态变量,进入main函数,都是这样的流程。 在gcc的环境中我们也可以是用汇编编写这样的文件,我们面前的选择有很多,当然我们没必要自己编写这些链接文件和启动代码,在之后的实际的工程建立中我会告诉大家实际的方法。不过在此之前我们还是要先把基础的内容学好再说。
其他的说明
在文件中我们看到了**_sidata、_sdata**等变量,这些变量在文件的前面部分被定义为外部:externunsignedlong_sidata;/*!< Start address for the initialization values of the .data section. */ externunsignedlong_sdata;/*!< Start address for the .data section */ externunsignedlong_edata;/*!< End address for the .data section */ externunsignedlong_sbss;/*!< Start address for the .bss section */ externunsignedlong_ebss;/*!< End address for the .bss section */ 而该文件却并未包含任何.h文件,那么他们从哪来的呢?细心的同学可能已经注意到了,我们之前提到过,这些变量的定义实际上都来自于ld文件中,他们在ld文件中被定义,最后链接器会将他们转换为实际的地址给我们的程序所使用的。 最后再说一下 attribute ((weak))属性,该属性表面其后的变量或是函数为弱申明,即在没有其他申明情况下调用改函数,而如果其他地方申明了,则会顶替该函数。所以在启动文件中,他们被用来修饰中断处理函为中断向量表提供一个默认的地址,而当用户定义后,就将地址转为用户定义的位置。
总结
说了这么多,这也是我们在这个系列中比较难以理解的部分,因为涉及到了GNU C的特性和计算机编译链接的最基础的部分,还有Cortex-M3内核工作的方式,但是请大家仔细的去理解学习,如果看了这篇文章还不懂那就多查查相关的资料,当你理解并贯通 这些知识时,你会发现原来在单片机上c语言是这样工作的,原来中断系统是这么的重要,你会发现单片机在你的眼前是如此的透彻。 在最后,我们还要说说,其实很多同学目前掌握的都是一个很简单的单片机应用方式,这都是被keil、IAR之流惯坏的,实际上在单片机背后,其实际的工作复杂而又充满着精致的设计,这点我们会在之后的nuttx系统使用中见到。 那时你会发现原来我们使用的M3单片机还有这么多的我们之前没用过的中断,原来m3的内核如此强大。对此我推荐大家还是学一遍51单片机的汇编教程,当你理解和使用过汇编后,你会更容易理解未来的讲解内容,同时也更容易理解此篇的内容。当然如果大家有兴趣可以先自己看看由宋岩前辈翻译的Cortex-M3 权威指南,来提前感受一下Cortex-M3内核的魅力。
审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。
举报投诉
-
单片机
+关注
关注
6001文章
43973浏览量
620835 -
STM32
+关注
关注
2239文章
10671浏览量
348743 -
编译器
+关注
关注
1文章
1577浏览量
48614
原文标题:STM32高级开发——链接器与启动文件
文章出处:【微信号:技术让梦想更伟大,微信公众号:技术让梦想更伟大】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
matlab的m文件编译为dll文件
');fprintf(fid,'%f',y);fclose('all');使用过mcc命令,但不能成功编译,是不是mcc编译器有局限性?对这样的m文件该用什么方法进行编译?求高手指导!
发表于 05-18 21:21
为什么链接器找不到库而C编译器可以?
使用makefile解析的行是:xc16-ld-o$(TARGET)。out main.o-lpic30是ld:.find-lpic30解析的错误,使用xc16-gcc我可以解析库文件。有人知道为什么
发表于 10-28 06:16
DSC编译器找不到链接文件是怎么回事?
我在 CW 11.1 上使用 56F83789-EVK 和 DSC 编译器。首先,我将一个目录链接到我的项目中。该目录包含通用代码,特别是我自己的文件 std.h。我的 main.
发表于 03-28 06:55
Bat 文件编译器
Bat 文件编译器汇编语言源程序代码:; BAT2EXEC.COM - a batch file compiler;; BAT2EXEC filename;; Revision
发表于 05-06 16:42
•6次下载
编译器是如何工作的_编译器的工作过程详解
随着计算机的发展,编译器已经发挥着十分重要的作用。本文主要介绍了编译器的种类、编译器的工作原理以及编译器工作的具体操作过程及步骤详解。
发表于 12-19 12:54
•1.5w次阅读
使用编译器将预处理文件的编译的命令是什么?
如果你使用的是集成开发环境,那么你点击编译按钮就可生成可执行文件,然后点击运行即可运行。那么,你知道从源代码到可执行文件经历了哪些过程吗。仅仅是编译
ASM源文件编译器软件免费下载
本文档的主要内容详细介绍的是ASM源文件编译器软件免费下载。适用于32位计算机,asm编译器,将ASM51.exe放在同一目录,在dos状态编译 如; d:asm51.exe ***.
发表于 08-07 08:00
•5次下载
浅谈hightec的编译链接文件
hightec的编译链接文件的后缀为ld,因此后文简称ld文件,ld文件主要分为三个部分:宏定义、MEMORY命令、SECTIONS命令。
评论