学习C语言这么久,一点点感悟

2019-07-20 14:44发布

这所谓的编译器就是将c语言代码编译为机器代码的,先将C编译为汇编代码,再由汇编器将汇编代码编译为机器代码,CPU执行的是机器代码
突然发觉好像很多书都这么说,很多人也这么说,于是很自然的记住了,但是,我突然想起了,这可是隐藏着一些道理。
1,C编译为汇编,对于这个过程,应该是平台无关的,具体是怎么实现的?这个是由C编译器开发商来处理,总之,如果我用IAR ARM的话,那么同样的main函数,编译出来应该是得到 ARM 格式的汇编代码,也就是说,使用的是 ARM 指令集,同样道理,如果是 GCC,也产生类似的,不过因为他们的内部实现并不一样,所以出来的结果可能有差异,不过最后的结果是一致的,也就是得到对应平台的汇编代码。
2,汇编器将汇编代码翻译为机器代码,其实这个过程比较简单的,有点像查表,一般厂家都会免费提供这么一个编译器,否则MCU就不用卖了,一般C编译都会自己实现一个。这让我想起了,什么是架构 Architecture,一个CPU怎么寻址,指令集是什么,堆栈指针是怎么变化的,PC程序指针是怎么变化的,怎么取指令,这些东西都是每个CPU区别于其他CPU所特有的东西,也就是一个CPU设计的时候所面临的内容。这就是架构,还记得学校学MCS-51的时候说得最多的是什么么?对,寻址和指令集,这个就是架构,老师想你学51这种架构,而不单纯是学怎么用这个芯片。
3,再说回上层的,ARM是一个核心,是一个架构,他制定了指令集,他指定了SP指针的访问方式,他指定了怎么寻址,所以说“ARM架构”
然后很多厂家根据ARM架构生产的芯片,因为你就一个核心没用啊?他就会执行指令,其他什么都不懂,我们需要外设,需要很多外围器件,例如I2C,USB什么的,和ARM这个核心一同构成一个完整意义上的芯片,我们叫 SOC (system on chip)单片系统,因为这个系统已经具备了基本跑起来的条件了,再在外围加上适当的复位和晶振电路,那基本就没有什么问题了。

从这我又想到了,那既然是同一个核心出来的,每个厂家生产芯片有什么不同呢?其实说白了就是外围器件的不同,例如ARM9芯片,三星的S3C2440,核心是 920T集成了LCD控制器,NAND FLASH 控制器,然而 Atmel的 AT91RM9200,核心同样是 ARM架构的 920T,但是他没有LCD控制器,却集成了网络方面的功能,这样看来,在一个架构上面不同厂家添加不同的功能器件,而得到了各种各样的芯片,也就是SOC了。
继续联想,不同的SOC之间其实还真的没有什么本质性的差别,第1,2点也说明了,C是万国语言,他到底也是通用的,所以同一段MAIN函数,如果你什么都不写,那么编译出来,在每一个soc都跑得通。而为什么基于这个芯片开发的程序就不能用到拿个芯片呢?其实一个重要的问题就是地址的访问,寻址的问题。ARM 32位总线固定了 4GB的寻址空间,但是ARM并没有硬性的规定哪个地址应该做什么,哪个地址不能做什么,具体怎么做,是SOC厂商说了算,所以产生的差异就是,可能这个soc里面的uart模块地址为 0x50000000 ,而另外一个厂商的soc的uart模块地址为 0x40000000 ,C语言的器件编程,说白了不就是对某个地址放某些数据,例如我要设置UART,很简单,将正确的数据送到正确的地址,也就是UART模块所在的地址,那么一切都完工了。
这里体现了很重要的一点,就是所谓的编程就是在什么地址放什么数据或者要什么数据。从这个抽象层面来看,编译器可以很轻松的编译同一个核心的不同SOC,因为不同核心,那牵涉到架构的问题,要修改编译器内部的实现代码,但是同一个架构同一个核心的,对不同芯片的访问的区别,就在于不同的地址!!
如果对S3C2440的编程,核心是 ARM 920T ,我们应该重点学习什么?
应该学习通用的,如果理解上面的内容,就知道什么是通用的了。那就是架构,搞懂了ARM 920T的架构,那么你使用的几个芯片又有什么差别呢?最多就是对着那个新的功能模块研究一下怎么设置。

4,再谈谈编译器
其实编译器实现的目的都是一个,机器代码,只是实现的办法和过程并不相同而已。那么对于众多的编译器,我们应该选择哪个?其实选择哪个都没有所谓,他们都同样的强大,ARMCC, GCC, IAR KEIL-MDK都同样强大,只要你学会了用,总能满足你的需要。但是,是不是每个编译器就各自为政呢?那倒不一定,所以呢,学习,是需要抓住核心
什么是核心?有几点
1)main函数执行之前究竟做了些什么
可能在学校很多老师都教,C语言的入口在main函数,所有的代码从这里开始!但是真的吗?其实并不是的,main函数执行之前需要必要的环境,这个环境由谁来提供?其实,在你的C语言程序连接的时候,一个初始化的模块(一般是叫 crt0.o ,也就是 c running time的缩写)已经悄悄的被连接进去了。这是每个编译器都会做的,关键是,这个crt0 的模块是编译器默认提供的还是你自己去实现。如果是编译器自带的,那么你应该怎么去做部分修改以达到自己的目的,如果是自己写,那又应该怎么去写?这就是学习的重点了。
初始化模块主要的工作:设置栈,设置堆(heap,主要是为C库的malloc服务),.data的搬移(如果需要的话),.bss段的初始化,跳转到 main函数。这个就是共性,无论什么编译器,都是在做这几步而已,不同的只是用各个编译器各自的语法去实现,但是本质也是不变的,当然,你也可以自己去实现,在连接的时候连接进来就OK了。
2)怎么处理连接(link)
编译的过程基本不需要去关心,编译器都做得好好的,你也没有干预的余地,最多就是传递一些不同的编译参数,这个稍微看看自带的文档,或者干脆用到的时候再翻,也问题不大。真正的大问题出在连接。
编译的过程得到的obj文件,是完全和地址无关的,例如,每个源文件对应的obj文件,地址都是从0开始的,它真正连接的时候才由连接器linker分配真正运行时候的地址,所以,你要想处理每个具体的代码段运行的时候应该在什么地方,就要注意学习link了。
其中指导linker工作的就是对应的脚本 linker scrpit,连接脚本,同样,这是一个共性,每个编译器都肯定有,只是实现办法的不同,脚本具体的编写的语法不同,所以这个是应该重点研究的,让你的代码听听话话,就花多点功夫去研究这个吧。

所以,每遇到一个编译器,先了解他怎么初始化,然后研究它怎么连接,了解了之后基本上你就会用这个编译器了,剩下的就不用说了,上课老师也说得够多了,怎么从main函数开始写,云云。
随笔,仅代表个人意见。

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。