c语言程序的执行从哪里开始问题,这里这里这两处怎么执行的

您好此题我在二级c语言程序的執行从哪里开始笔试中遇到过,原题如下:

一个c语言程序的执行从哪里开始的执行是从:()

本程序的主函数开始,到本程序的主函数结束

本程序嘚第一个函数开始,到本程序的最后一个函数结束

本程序的主函数开始,到本程序的最后一个函数结束

本程序的第一个函数开始,到本程序的主函数结束

你对这个回答的评价是

下载百度知道APP,抢鲜体验

使用百度知道APP立即抢鲜体验。你的手机镜头里或许有别人想知道的答案

1. 下面代码是系统启动后U-boot上电后运荇的第一段代码他是什么意思?

他们是系统定义的异常一上电程序跳转到reset异常处执行相应的汇编指令,下面定义出的都是不同的异常比如软件发生软中断时,CPU就会去执行软中断的指令这些异常中断在CUP中地址是从0开始,每个异常占4个字节

操作系统先注册一个总的中斷,然后去查是由哪个中断源产生的中断再去查用户注册的中断表,查出来后就去执行用户定义的用户中断处理函数

undefined_instruction表示未定义的这個异常是由.word来定义的,它表示定义一个字一个32位的数,.word后面的数表示把该标识的编译地址写入当前地址标识是不占用任何指令的。把標识存放的数值copy到指针pc上面那么标识上存放的值是什么?是由.word undefined_instruction来指定的pc就代表你运行代码的地址,她就实现了CPU要做一次跳转时的工作

什么是编译地址?什么是运行地址

32位的处理器,它的每一条指令是4个字节以4个字节存储顺序,进行顺序执行CPU是顺序执行的,只要沒发生什么跳转它会顺序进行执行,编译器会对每一条指令分配一个编译地址这是编译器分配的,在编译过程中分配的地址我们称の为编译地址。

运行地址是指程序指令真正运行的地址,是由用户指定的用户将运行地址烧录到哪里,哪里就是运行的地址比如有┅个指令的编译地址是0x5,实际运行的地址是0x200如果用户将指令烧到0x200上,那么这条指令的运行地址就是0x200当编译地址和运行地址不同的时候會出现什么结果?结果是不能跳转编译后会产生跳转地址,如果实际地址和编译后产生的地址不相等那么就不能跳转。c语言程序的执荇从哪里开始编译地址都希望把编译地址和实际运行地址放在一起的但是汇编代码因为不需要做c语言程序的执行从哪里开始到汇编的转換,可以认为的去写地址所以直接写的就是他的运行地址,这就是为什么任何bootloader刚开始会有一段汇编代码因为起始代码编译地址和实际哋址不相等,这段代码和汇编无关跳转用的运行地址。编译地址和运行地址如何来算呢假如有两个编译地址a=0x10,b=0x7b的运行地址是0x300,那么a嘚运行地址就是b的运行地址加上两者编译地址的差值a-b=0x10-0x7=0x3,a的运行地址就是0x300+0x3=0x303

假设uboot上两条指令的编译地址为a=0x和b=0x,这两条指令都落在bank6上现在偠计算出他们对应的运行地址,要找出运行地址的始地址这个是由用户烧录进去的,假设运行地址的首地址是0x0则a的运行地址

为0x7,b为0x1僦是这样算出来的。

为什么要分配编译地址这样做有什么好处,有什么作用

比如在函数a中定义了函数b,当执行到函数b时要进行指令跳轉要跳转到b函数所对应的起始地址上去,编译时编译器给每条指令都分配了编译地址,如果编译器已经给分配了地址就可以直接进行跳转查找b函数跳转指令所对应的表,进行直接跳转因为有个编译地址和指令对应的一个表,如果没有分配编译器就查找不到这个跳轉地址,要进行计算非常麻烦。

以NOR Flash为例NOR Falsh是映射到bank0上面,SDRAM是映射到bank6上面uboot和内核最终是在SDRAM上面运行,最开始我们是从Nor Flash的零地址开始往后燒录uboot中至少有一段代码编译地址和运行地址是不一样的,编译uboot或内核时都会将编译地址放入到SDRAM中,他们最终都会在SDRAM中执行刚开始uboot在Nor FlashΦ运行,运行地址是一个低端地址是bank0中的一个地址,但编译地址是bank6中的地址这样就会导致绝对跳转指令执行的失败,所以就引出了相對地址的概念那么什么是相对地址呢?至少在bank0中uboot这段代码要知道不能用b+编译地址这样的方法去跳转指令因为这段代码的编译地址和运荇地址不一样,那如何去做呢要去计算这个指令运行的真实地址,计算出来后再做跳转应该是b+运行地址,不能出现b+编译地址而是b+运荇地址,而运行地址是算出来的

这段话表示,用户告诉编译器编译地址的起始地址

1. 设置CPU的启动模式

//设置CPU进入管理模式 即设置相应的CPSR程序狀态字

关闭看门狗关闭中断,所谓的喂狗是每隔一段时间给某个寄存器置位而已在实际中会专门启动一个线程或进程会专门喂狗,当仩层软件出现故障时就会停止喂狗停止喂狗之后,cpu会自动复位一般都在外部专门有一个看门狗,做一个外部的电路不在cpu内部使用看門狗,cpu内部的看门狗是复位的cpu当开发板很复杂时,有好几个cpu时就不能完全让板子复位,但我们通常都让整个板子复位看门狗每隔短時间就会喂狗,问题是在两次喂狗之间的时间间隔内运行的代码的时间是否够用,两次喂狗之间的代码是否在两次喂狗的时间延迟之内如果在延迟之外的话,代码还没改完就又进行喂狗代码永远也改不完。

//关闭看门狗的实际代码

3. 屏蔽所有中断为什么要关中断?中断處理中ldr pc是将代码的编译地址放在了指针上而这段时间还没有搬移代码,所以编译地址上面没有这个代码如果进行跳转就会跳转到空指針上面

3. 设置时钟分频,为什么要设置时钟起始可以不设,系统能不能跑起来和频率没有任何关系频率的设置是要让外围的设备能承受所设置的频率,如果频率过高则会导致cpu操作外围设备失败

为什么要关闭catch和MMU呢catch和MMU是做什么用的?

Catch是cpu内部的一个2级缓存她的作用是将常用嘚数据和指令放在cpu内部,MMU是用来做虚实地址转换用的我们的目的是设置控制的寄存器,寄存器都是实地址如果既要开启MMU又要做虚实地址转换的话,中间还多一步

先要把实地址转换成虚地址,然后再做设置但对uboot而言就是起到一个简单的初始化的作用和引导操作系统,洳果开启MMU的话很麻烦,也没必要所以关闭MMU.

说道catch就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到他的本质是告诉编译器不要對我的代码进行优化,优化的过程是将常用的代码取出来放到catch中它没有从实际的物理地址去取,它直接从cpu的缓存中去取但常用的代码僦是为了感知一些常用变量的变化,如果正在取数据的时候发生跳变那么就感觉不到变量的变化了,所以在这种情况下要用Volatile关键字告诉編译器不要做优化每次从实际的物理地址中去取指令,这就是为什么关闭catch关闭MMU但在c语言程序的执行从哪里开始中是不会关闭catch和MMU的,会咑开如果编写者要感知外界变化,或变化太快从catch中取数据会有误差,就加一个关键字Volatile

6.设置完毕后拷贝uboot代码到4k空间,拷贝完毕后执行內存中的uboot代码

以上流程基本上适用于所有的bootloader这就是step1阶段

7. 问题:如果换一块开发板有可能改哪些东西?

首先cpu的运行模式,如果需要对cpu进荇设置那就设置管看门狗,关中断不用改时钟有可能要改,如果能正常使用则不用改关闭catch和MMU不用改,设置bank有可能要改最后一步拷貝时看地址会不会变,如果变化也要改执行内存中代码,地址有可能要改

8. Nor Flash和Nand Flash本质区别就在于是否进行代码拷贝,也就是下面代码所表述:无论是Nor Flash还是Nand Flash核心思想就是将uboot代码搬运到内存中去运行,但是没有拷贝bss后面这段代码只拷贝bss前面的代码,bss代码是放置全局变量的Bss段代码是为了清零,拷贝过去再清零重复操作

9. 看一下uboot.lds文件在board/smdk2410目录下面,uboot.lds是告诉编译器这些段改怎么划分GUN编译过的段,最基本的三个段昰RORW,ZIRO表示只读,对应于具体的指代码段RW是数据段,ZI是归零段就是全局变量的那段。Uboot代码这么多如何保证start.s会第一个执行,编译在朂开始呢就是通过uboot.lds链接文件进行

.text : //test指代码段,上面3行标识是不占用任何空间的

译时放到最开始这就是为什么把uboot烧到起始地址上它肯定运荇的是start.s

. = ALIGN(4); //前面的 “.” 代表当前值,是计算一个当前的值是计算上

面占用的整个空间,再加一个单元就表示它现在的位置

回忆一下GUN在编译代碼时的四个步骤1.预处理,2.编译3.汇编,4.链接链接就做的是这个文件的动作,就是将这些文件重新map一下分配地址

原标题:对c语言程序的执行从哪裏开始程序进行调试的基本方法

调试程序的方法与医生看病的道理类似:先问清基本情况再进行大致的检查,然后分析检查的结果、确萣范围再进行专项检查,再分析检查结果如此反复,最后确定问题所在并进行治疗、检查疗效

必须指出的是:用户调试自己的程序時,应对程序的设计(工作)思路非常清楚知道每一段、每一行程序所应起到(尽管不见得都能实现)的作用,这是基本的前提若自己对设计嘟不清楚、甚至不知道每一段、每一行程序应发挥的作用,是谈不上调试程序的

一. 观察了解程序的“病症”表现

首先是看清情况,程序嘚任务、程序的预期表现与程序工作的实际表现大概是什么方面的“病”——对于常见的小“病”,经验丰富的专家不用后续检查就能知道问题所在经验当然重要,但对于初学者而言掌握正确的调试思路则更加重要,因为初学者很难通过观察程序而发现问题所在

二. 弄清程序的主要工作流程

在学习过程中设计的程序一般都不太复杂,从总体算法上总是可以划分为几个大的模块(也可称为步骤可以是一段程序或一个子程序——函数):接收用户的要求和任务(读取相应的参数、输入相应的数据)、对数据进行计算和处理、按格式要求输出相应嘚结果。对于每一个大的模块又可以分为许多子模块。

例如上面的程序是有问题的它是为了实现以下功能(其中的注释写明了主要模塊的功能以及每个模块的实现方法):

程序运行时先显示Please input numbers:,再从键盘上读入一组整数(只考虑int型)数与数之间只使用空格或回车作分隔。数鈳正可负最多10000个,但若读入的数为-222时则表示输入结束且-222不算在该组数内。

对这一组数按从小到大的顺序进行排序

将排序后的这一组數输出到屏幕上,输出格式为每行6个数数与数之间使用逗号(,)分隔,两个逗号之间的宽度(不算逗号)为6且使用左对齐格式注意,行尾没有逗号

三. 进行大致的检查,确定问题存在的模块

检查的任务就是查看程序的实际工作状态(屏幕输出是否正确、各变量的值是否正确)与预期的设计是否一致,若不一致则肯定有问题。

对于较长、较复杂的程序检查时不应从开始一行一行检查,这种方法效率低、不科学吔不易发现问题。正确的方法是:先分大模块检查确定大模块有无问题,再针对有问题的大模块检查其内部的工作过程。例如对于程序3-1,应先检查输入完成时工作是否正确即让程序运行至“计算与处理”时暂停(从键盘输入一组数据),查看相应的结果(这段程序的运行目的就是将输入数据存放至数组a中并由num记录数据个数因此应检查数组a和num的内容)是否正确,若不正确则至少找到一部分问题。排除输入嘚故障后则可让程序运行到“输出”时暂停,检查相应的结果(即数组a中的数据是否按要求排好顺序)

在检查过程中,用户应根据自己的經验灵活调整检查策略,提高工作效率例如可以使用二分法定位故障,也可观察后估计问题位置再进行检查

四. 检查故障模块,确定問题并解决

对于复杂故障模块内部的运行检查可以再分子模块(部分)进行分部检查。检查模块的设计是否正确的基本思路是:一步一步运荇程序看程序的运行流程是否如设计期望,看每步程序的运行结果(屏幕输出和相关变量)是否与设计(心算)的一致例如程序3-1的输入部分,假定未看到问题则可检查:输入一个数据后,x中的数据是否是输入的数据——若不是则该条语句肯定有问题,仔细检查应能发现问题;当输入不是结束标志时则否将数据存入了a[i]、i和计数器num的值是否正确;当输入的是结束标志时,是否如期望的结束输入

我要回帖

更多关于 c语言程序的执行从哪里开始 的文章

 

随机推荐