当前位置:首页 > C语言
  • 如何学好单片机C语言?你知道吗?

    如何学好单片机C语言?你知道吗?

    你知道怎么学习单片机C语言吗?很多想学单片机的人问我的第一句话就是怎样才能学好单片机?对于这个问题我今天就我自己是如何开始学单片机,如何开始上手,如何开始熟练这个过程给大家讲讲。 先说说单片机,一般我们现在用的比较多的的MCS-51的单片机,它的资料比较多,用的人也很多,市场也很大。就我个人的体会怎么样才能更快的学会单片机这门课。单片机这门课是一项非常重视动手实践的科目,不能总是看书,但是学习它首先必须得看书,因为从书中你需要大概了解一下,单片机的各个功能寄存器,而说明白点,我们使用单片机就是用软件去控制单片机的各个功能寄存器,再说明白点,就是控制单片机那些管脚的电平什么时候输出高,什么时候输出低。 由这些高低电平的变化来控制你的系统板,实现我们需要的各个功能。至于看书,只需大概了解单片机各管脚都是干什么的?能实现什么样的功能?第一次,第二次你可能看不明白,但这不要紧,因为还缺少实际的感观认识。所以我总是说,学单片机看书看两三天的就够了,看小说你一天能看五六本,看单片机你两三天看两三遍就够了,可以不用仔细的看。推荐一本书,就这一本就足够,书名是《新编MCS-51单片机应用设计》,是哈尔滨工业大学出版社出的的,作者是张毅刚。大概了解一下书上的内容,然后实践,这是非常关键的。 如果说学单片机你不实践那是不可能学会的,关于实践有两种方法你可以选择,一种方法:你自己花钱买一块单片机的学习板,不要求功能太全的,对于初学者来说你买功能非常多的那种板子,上面有很多东西你这辈子都用不着,我建议有流水灯、数码管、独立键盘、矩阵键盘、AD或DA(原理一样)、液晶、蜂鸣器,这就差不多了。如果上面我提到的这些,你能熟练应用,那可以说对于单片机方面的硬件你已经入门了,剩下的就是自己练习设计电路,不断的积累经验。 只要过了第一关,后面的路就好走多了,万事开头难,大家可能都听过。方法二:你身边如果有单片机方面的高手,向他求助,让他帮你搭个简单的最小系统板。对于高手来说,做个单片机的最小系统板只需要一分钟的时间,而对于初学者可就难多了,因为只有对硬件了解了,才能熟练运用。而如果你身边没有这样的高手,又找不到可以帮助你的人,那我劝你最好是自己买上一块,毕竟自己有一块要方便的多,以后做单片机类的小实验时都能用得上,还省事。 有了单片机学习板之后你就要多练习,最好是自己有台电脑,一天少看电影,少打游戏,把学习板和电脑连好,打开调试软件坐在电脑前,先学会怎么用调试软件,然后从最简单的流水灯实验做起,等你能让那八个流水灯按照你的意愿随意流动时你已经入门了,你会发现单片机是多么迷人的东西啊,太好玩了,这不是在学习知识,而是在玩,当你编写的程序按你的意愿实现时你比做什么事都开心,你会上瘾的,真的。做电子类的人真的会上瘾。然后让数码管亮起来,这两项会了后,你已经不能自拔了,你已经开始考虑你这辈子要走哪一行了。 就是要这样练习,在写程序的时候你肯定会遇到很多问题,而这时你再去翻书找,或是问别人,当得到解答后你会记住一辈子的,知识必须用于现实生活中,解决实际问题,这样才能发挥它的作用,你自己好好想想,上了这么多年大学,天天上课,你在课堂上学到了什么?是不是为了期末考试而忙碌呢?考完得了90分,哈哈哈好高兴啊,下学期开学回来忘的一干二净,是不是?你学到什么了?但是我告诉你单片机一旦学会,永远不会忘了。另外我再说说用汇编和C语言编程的问题。很多同学大一二就开设了C语言的课,我也上过,我知道那时天天就是几乘几,几加几啊,求个阶乘啊。 学完了有什么用?让你用C语言编单片机的程序你是不是就傻了?书上的东西我们必须要会运用。单片机编程用C语言或汇编语言都可以,但是我建议用C语言比较好,如果原来有C语言的基础那学起来会更好,如果没有,也可以边学单片机边学C语言,C语言也挺简单,只是一门工具而已,我劝你最好学会,将来肯定用得着,要不你以后也得学,你一点汇编都不会根本无所谓,但你一点C语言都不会那你将来会吃苦头。 汇编写程序代码效率高,但相对难度较大,而且很罗嗦,尤其是遇到算法方面的问题时,根本是麻烦的不得了,现在单片机的主频在不断的提高,我们完全不需要那么高效率的代码,因为有高频率的时钟,单片机的ROM也在不断的提高,足够装得下你用C语言写的任何代码,C语言的资料又多又好找,将来可移植性非常好,只需要变一个IO口写个温度传感器的程序在哪里都能用,所以我劝大家用C语言。 总结上面,只要你有信心,做事能坚持到底,有不成功不放弃的强烈意志,那学个单片机来说就是件非常容易的事。 步骤 1.找本书大概了解一下单片机结构,大概了解就行。不用都看懂,又不让你出书的。(三天) 2.找学习板练习编写程序,学单片机就是练编程序,遇到不会的再问人或查书。 (二十天) 3.自己网上找些小电路类的资料练习设计外围电路。焊好后自己调试,熟悉过程。 (十天) 4.自己完全设计具有个人风格的电路、产品,你已经是高手了。 看到了吗?下功夫一个多月你就能成为高手,我就讲这么多了,学不学得会,下不下得了功夫就看你的了。 我的单片机学习心得 很多人说,学单片机最好先学汇编语言,以我的经验告诉大家,绝对没有这个必要,初学者一开始就直接用C语言为单片机编程,既省时间,学起来又容易,进步速度会很快。在刚开始学单片机的时候,千万不要为了解单片机内部结构而浪费时间,这样只能打击你的信心,当你学会编程后,自然一步步就掌握其内部结构了。 单片机的学习实践 单片机提高重在实践,想要学好单片机,软件编程必不可少。但是熟悉硬件对于学好单片机的也是非常重要的。如何学习好硬件,动手实践是必不可少的。我们可以通过自己动手做一个自己的电子制作,通过完成它,以提高我的对一些芯片的了解和熟练运用它。这样我们就可以多一些了解芯片的结构。我相信,你完成了一个属于自己的电子制作,你的单片机水平就会有一个质的提高。 这就是我学习单片机的心得体会,希望给单片机的爱好者学好单片机有所帮助。 使用单片机就是理解单片机硬件结构,以及内部资源的应用,在汇编或C语言中学会各种功能的初始化设置,以及实现各种功能的程序编制。 第一步:数字I/O的使用 使用按钮输入信号,发光二极管显示输出电平,就可以学习引脚的数字I/O功能,在按下某个按钮后,某发光二极管发亮,这就是数字电路中组合逻辑的功能,虽然很简单,但是可以学习一般的单片机编程思想,例如,必须设置很多寄存器对引脚进行初始化处理,才能使引脚具备有数字输入和输出输出功能。每使用单片机的一个功能,就要对控制该功能的寄存器进行设置,这就是单片机编程的特点,千万不要怕麻烦,所有的单片机都是这样。 第二步:定时器的使用 学会定时器的使用,就可以用单片机实现时序电路,时序电路的功能是强大的,在工业、家用电气设备的控制中有很多应用,例如,可以用单片机实 现一个具有一个按钮的楼道灯开关,该开关在按钮按下一次后,灯亮3分钟后自动灭,当按钮连续按下两次后,灯常亮不灭,当按钮按下时间超过2s,则灯灭。数 字集成电路可以实现时序电路,可编程逻辑器件(PLD)可以实现时序电路,可编程控制器(PLC)也可以实现时序电路,但是只有单片机实现起来最简单,成本最低。定时器的使用是非常重要的,逻辑加时间控制是单片机使用的基础。 第三步:中断 单片机的特点是一段程序反复执行,程序中的每个指令的执行都需要一定的执行时间,如果程序没有执行到某指令,则该指令的动作就不会发生,这样就会耽误很多快速发生的事情,例如,按钮按下时的下降沿。要使单片机在程序正常运行过程中,对快速动作做出反应,就必须使用单片机的中断功能,该功能就是在快速动作发生后,单片机中断正常运行的程序,处理快速发生的动作,处理完成后,在返回执行正常的程序。 中断功能使用中的困难是需要精确地知道什么时候不允许中断发生(屏蔽中断)、什么时候允许中断发生(开中断),需要设置哪些寄存器才能使某种中断起作用,中断开始时,程序应该干什么,中断完成后,程序应该干什么等等。中断学会后,就可以编制更复杂结构的程序,这样的程序可以干着一件事,监视着一件事,一旦监视的事情发生,就中断正在干的事情,处理监视的事情,当然也可以监视多个事情,形象的比喻,中断功能使单片机具有吃着碗里的,看着锅里的功能。 以上三步学会,就相当于降龙十八掌武功,会了三掌了,可以勉强护身。 第四步:与PC机进行RS232通信 单片机都有USART接口,特别是MSP430系列中很多型号,都具有两个USART接口。USART接口不能直接与PC机的RS232接口连接,它们之间的逻辑电平不同,需要使用一个MAX3232芯片进行电平转换。 USART接口的使用是非常重要的,通过该接口,可以使单片机与PC机之间交换信息,虽然RS232通信并不先进,但是对于接口的学习是非常重要的。正确使用USART接口,需要学习通信协议,PC机的RS232接口编程等等知识。试想,单片机实验板上的数据显示在PC机监视器上,而PC机的键盘信号可以在单片机实验板上得到显示,将是多么有意思的事情啊! 第五步:学会A/D转换 MAP430单片机带有多通道12位A/D转换器,通过这些A/D转换器可以使单片机操作模拟量,显示和检测电压、电流等信号。学习时注意模拟地与数字地、参考电压、采样时间,转换速率,转换误差等概念。使用A/D转换功能的简单的例子是设计一个电压表。 第六步:学会PCI、I2C接口和液晶显示器接口 这些接口的使用可以使单片机更容易连接外部设备,在扩展单片机功能方面非常重要。 第七步:学会比较、捕捉、PWM功能 这些功能可以使单片机能够控制电机,检测转速信号,实现电机调速器等控制起功能。如果以上七步都学会,就可以设计一般的应用系统,相当于学会十招降龙十八掌,可以出手攻击了。 第八步:学习USB接口、TCP/IP接口、各种工业总线的硬件与软件设计。 学习USB接口、TCP/IP接口、各种工业总线的硬件与软件设计是非常重要的,因为这是当前产品开发的发展方向。到此为止,相当于学会15招降龙十八掌,但还不到打遍天下无敌手的境界。即使如此,也算是单片机大虾了!!以上就是单片机C语言学习的方法,希望能给大家帮助。

    时间:2020-05-14 关键词: 单片机 C语言 学习架构

  • 如何学好单片机?从入门到高手的进阶方法

    如何学好单片机?从入门到高手的进阶方法

    你知道如何学好单片机吗?无论是作为一名业余的电子爱好者还是一名电子行业的相关从业人员,掌握单片机技术无疑可以使您如虎添翼,为您的电子小制作或者开发设计电子产品时打开方便的大门!学习单片机技术有一定的难度,不花费一番努力是很难学会的,但是只要不断努力就一定能成功,套用一句广告歌词:努力总有回报! 第一步:基础理论知识学习 基础理论知识包括模拟电路、数字电路和C语言知识。模拟电路和数字电路属于抽象学科,要把它学好还得费点精神。在你学习单片机之前,觉得模拟电路和数字电路基础不好的话,不要急着学习单片机,应该先回顾所学过的模拟电路和数字电路知识,为学习单片机加强基础。否则,你的单片机学习之路不仅会很艰难和漫长,还可能半途而废。 笔者始终认为,扎实的电子技术基础是学好单片机的关键,直接影响单片机学习入门的快慢。有些同学觉得单片机很难,越学越复杂,最后学不下去了。有的同学看书时似乎明白了,可是动起手来却一塌糊涂,究其原因就是电子技术基础没有打好,首先被表面知识给困惑了。 单片机属于数字电路,其概念、术语、硬件结构和原理都源自数字电路,如果数字电路基础扎实,对复杂的单片机硬件结构和原理就能容易理解,就能轻松地迈开学习的第一步,自信心也会树立起来。相反,基础不好,这个看不懂那个也弄不明白,越学问题越多,越学越没有信心。如果你觉得单片机很难,那就应该先放下单片机教材,去重温数字电路,搞清楚触发器、寄存器、门电路、COMS电路、时序逻辑和时序图、进制转换等理论知识。理解了这些知识之后再去看看单片机的结构和原理,我想你会大彻大悟,信心倍增。 模拟电路是电子技术最基础的学科,她让你知道什么是电阻、电容、电感、二极管、三极管、场效应管、放大器等等以及它们的工作原理和在电路中的作用,这是学习电子技术必须掌握的基础知识。一般是先学习模拟电路再去学习数字电路。扎实的模拟电路基础不仅让你容易看懂别人设计的电路,而且让你的设计的电路更可靠,提高产品质量。 C语言知识并不难,没有任何编程基础的人都可以学,在我看来,初中生、高中生、中专生、大学生都能学会。当然,数学基础好、逻辑思维好的人学起来相对轻松一些。C语言需要掌握的知识就那么3个条件判断语句、3个循环语句、3个跳转语句和1个开关语句。别小看这10个语句,用他们组合形成的逻辑要多复杂有多复杂。学习时要一条语句一条语句的学,学一条活用一条,全部学过用过这些关键语句后,相信你的C基础建立了。 当基础打好以后,你会感觉到单片机不再难学了,而且越学越起劲。当单片机乖乖的依照你的逻辑思维和算法去执行指令,实现预期控制效果的时候,成就感会让你信心十足、夜以续日、废寝忘食的投入到单片机的世界里。可以这么说,扎实的电子技术基础和C语言基础能增强学习单片机信心,较快掌握单片机技术。 第二步:单片机实践 这是真正学习单片机的过程,既让人兴奋又让人疲惫,既让人无奈又让人不服,既让人孤独又让人充实,既让人气愤又让人欣慰,既有失落感又有成就感。其中的酸甜苦辣只有学过的人深有体会。思想上要有刻苦学习的决心,硬件上要有一套完整的学习开发工具,软件上要注重理论和实践相结合。 1.有刻苦学习的决心 首先,明确学习目的。先认真回答两个问题:我学单片机来做什么?需要多长时间把它学会?这是你学单片机的动力。没有动力,我想你坚持不了多久。 其次,端正学习心态。单片机学习过程是枯燥乏味、孤独寂寞的过程。要知道,学习知识没有捷径,只有循序渐进,脚踏实地,一步一个脚印,才能学到真功夫。再次,要多动脑勤动手。单片机的学习具有很强的实践性,是一门很注重实际动手操作的技术学科。不动手实践你是学不会单片机的。 最后,虚心交流。在单片机学习过程中每个人都会遇到无数不能解决的问题,需要你向有经验的过来人虚心求教,否则,一味的自己埋头摸索会走许多弯路,浪费很多时间。 2.有一套完整的学习开发工具 学习单片机是需要成本的。必须有一台电脑、一块单片机开发板(如果开发板不能直接下载程序代码的话还得需要一个编程器)、一套视频教程、一本单片机教材和一本C语言教材。电脑是用来编写和编译程序,并将程序代码下载到单片机上;开发板用来运行单片机程序,验证实际效果;视频教程就是手把手教你单片机开发环境的使用、单片机编程和调试。对于单片机初学者来说,视频教程必须看,要不然,哪怕把教材看了几遍,还是不知道如何下手,尤其是院校里的单片机教材,学了之后,面对真正的单片机时可能还是束手无策;单片机教材和C语言教材是理论学习资料,备忘备查。不要为了节约成本不用开发板而光用Protur软件仿真调试,这和纸上谈兵没什么区别。 3. 要注重理论和实践相结合 单片机C语言编程理论知识并不深奥,光看书不动手也能明白。但在实际编程的时候就没那么简单了。一个程序的形成不仅需要有C语言知识,更多需要融入你个人的编程思路和算法。编程思路和算法决定一个程序的优劣,是单片机编程的大问题,只有在实际动手编写的时候才会有深切的感悟。一个程序能否按照你的意愿正常运行就要看你的思路和算法是否正确、合理。如果程序不正常则要反复调试(检查、修改思路和算法),直到成功。这个过程耗时、费脑、疲精神,意志不坚强者往往被绊倒在这里半途而废。 学习编写程序应该按照以下过程学习,效果会更好。看到例程题目先试着构思自己的编程思路,然后再看教材或视频教程里的代码,研究人家的编程思路,注意与自己思路的差异;接下来就照搬人家的思路亲自动手编写这个程序,领会其中每一条语句的作用;对有疑问的地方试着按照自己的思路修改程序,比较程序运行效果,领会其中的奥妙。每一个例程都坚持按照这个过程学习,你很快会找到编程的感觉,取其精华去其糟粕,久而久之会形成你独特的编程思想。当然,刚开始,看别人的程序源代码就像看天书一样,只要硬着头皮看,看到不懂的关键字和语句就翻书查阅、对照。只要能坚持下来,学习收获会事半功倍。 在实践过程中不仅要学会别人的例程,还要在别人的程序上改进和拓展,让程序产生更强大的功能。同时,还要懂得通过查阅芯片数据手册(DATASHEET)里有关芯片命令和数据的读写时序来核对别人例程的可靠性,如果你觉得例程不可靠就把它修改过来,成为是你自己的程序。不仅如此,自己应该经常找些项目来做,以巩固所学的知识和积累更多的经验。 第三步:单片机硬件设计 当编写自己的程序信手拈来、阅读别人的程序能够发现问题的时候,说明你的单片机编程水平相当不错了。接下来就应该研究的硬件了。硬件设计包括电路原理设计和PCB板设计。学习做硬件要比学习做软件麻烦,成本更高,周期更长。但是,学习单片机的最终目的是做产品开发----软件和硬件相结合形成完整的控制系统。所以,做硬件也是学习单片机技术的一个必学内容。 电路原理设计涉及到各种芯片的应用,而这些芯片外围电路的设计、典型应用电路和与单片机的连接等在芯片数据手册(DATASHEET)都能找到答案,前提是要看得懂全英文的数据手册。否则,照搬别人的设计永远落在别人的后面,你做的产品就没有创意。电子技术领域的第一手资料(DATASHEET)都是英文,从第一手资料里你所获得的知识可能是在教科书、网络文档和课外读物等所没有的知识。虽然有些资料也都是在DATASHEET的基础上撰写的,但内容不全面,甚至存在翻译上的遗漏和错误。当然,阅读DATASHEET需要具备一定的英文阅读能力,这也是阻碍单片机学习者晋级的绊脚石。良好的英文阅读能力能让你在单片机技术知识的海洋里自由遨游。 做PCB板就比较简单了。只要懂得使用Protel软件或 AltimDesigner软件就没问题了。但要想做的板子布局美观、布线合理还得费一番功夫了。娴熟的单片机C语言编程、会使用Protel软件或 AltimDesigner软件设计PCB板和具备一定的英文阅读能力,你就是一个遇强则强的单片机高手了。以上就是学好单片机的一些方法,希望能给大家帮助。

    时间:2020-05-13 关键词: 单片机 C语言 模拟电路

  • 51单片机的学习方法,你真的会吗?

    51单片机的学习方法,你真的会吗?

    什么是51单片机?应该如何学习?作为一名入门级的工程师,万事开头难,只要知道学习51单片机需要学习哪些知识点就万事大吉了。然后再系统的将知识点全部掌握即可。那么我们一起看看吧,到底要学习哪些呢? 实际上,其实不需要多少东西,会简单的C语言,知道51单片机的基本结构就可以了。一般的大学毕业生都可以了,自学过这2门课程的高中生也够条件。设备上,一般是建议购买一个仿真器,例如,的“双功能下载线”就具有良好的稳定性和较快的下载速度,上位机可扩展,可以下载更多的单片机及嵌入式芯片。通过实验,这样才可以进行实际的,全面的学习。日后在工作上,仿真器也大有用处。还有,一般光有仿真器是不行,还得有一个实际的电路,即学习板,如图,即为,单片机最小系统。 学习板以强大的接口为主,单片机的学习分两方面,一方面是单片机的原理及内部结构,另一方面是单片机的接口技术。这些都是需要平时多积累,多动手,多思考,这样才能学好单片机技术。 单片机学习的4个阶段 一、整体了解 要知道 单片机是什么?单片机有何用?如何系统学习单片机?单片机系统设计的流程是怎样的,需要掌握哪些辅助软件? 了解这些之后,我们的学习就有了目标和方向。 二、揭秘 单片机很难学,是因为其内部结构、编程语言抽象,且实际应用中与其他电子技术和元器件知识相互关联,需结合起来一起设计开发产品。所以,第二阶段要了解单片机的内部结构是怎样的?单片机开发经常会用到哪些电子技术和元器件知识?如何将一条条编程指令组合成一段段有效的程序? 三、解密 之所以单片机能成为控制核心,设计出包罗万象的应用系统来,是因为开发者利用了单片机提供的种种功能及各种外设。所以,第三阶段我们要掌握单片机的各种功能,再加上诸如传感器、模数转换、扫描显示、串行、中断的应用思维,结合更多的元器件、电子电路知识,逐个学习、体会实际的单片机系统的秘密。 四、远航 通过以上三个阶段,读者基本就可掌握单片机的应用了。但要设计出丰富的单片机系统,解决复杂的实际问题,还需要了解更多的外设知识及其与单片机的联系(如电动机、各类 存储器、继电器、红外管等)。这些需要不断的学习和积累。有时候,接到一些开发任务,就需要你针对这个任务自觉地去搜集、学习相关知识,在实践中不断学习和提高。以上就是51单片机的学习方法解析,希望能给大家帮助。

    时间:2020-05-13 关键词: 51单片机 C语言 嵌入式芯片

  • 浅谈Linux系统编程语言以及学习经验

    浅谈Linux系统编程语言以及学习经验

    作为初学者,我想记录一下我的学习状况,一是可以回头寻找自己的进步,二是希望我可以通过这种学习方式来指导未来想学的伙伴们。 首先,作为一个初学者,必须扎扎实实的掌握一门基础的编程,计算机语言想通相似,想要学好编程,必须熟练地掌握一门基础。 语言,这里推荐学习C和C++;C++作为一门最难的语言,能掌握它,其他语言自然不在话下。 在今天,我强烈推荐大家系统学习AI以及算法,这是一个编程的灵魂所在,掌握了算法,在你掌握语法知识的基础上,You can become a better “搬砖工” 当然,系统的操作自然是必不可少的。windows学会用来撩妹是最适合不过的了,linux对于外行人装逼是最适合不过的了,满屏幕的源代码有木有大佬的既视感。 这里强行推荐一个网站:https://www.linuxprobe.com/,上面有大量的精华帖子值得你去浏览,去学习,而且刘老师定期发布一些精选视频,使得学习事半功倍。如果能购买一本《linux就该这么学》更合适不过了。 作为一个资深的小白,我在小白这个阶段一直逗留,没有进阶,说说我的失败之路,大家请不要效仿 第一,一定要去系统的学习,千万不要东拼西凑,学习最忌讳学的杂乱无章,点无法连成线就永远只是一个点,一个在教育界摸爬滚打多年 “老司机”告诉我,学习切不可杂乱无章,务求精,不可求多,在一个“T”型学习的今天,各种信息繁杂冗多,我们的学习的宽度自然是没有任何问题的,但是学习的深度呢,怕是远远不及,所以我们学习就像撩妹,你不但要知道她的宽度,更多是要知道她的深度。 说说我在这一年了解了什么吧,matlab,c,c++,linux,kali linux,大数据下的oracle,hadoop,gonldgate,html,....但是真真了解怕是没有多少,所以作为一名资深的小白,我用亲身经历告诉你,切忌!!!学习繁杂,一定要精,在平时胡乱研究这些,反而什么都没学到,还丢掉了学习,成为了一名学校的学渣。 第三,作为初学者,加一些技术群,作为初学者,经常水群,你可以了解到一些各种学习的渠道,然后通过学习的渠道,可以获取各种各样广泛的资料。 第四,最好能加一些技术论坛,多看一些技术类文章,多多关注一些行业新闻,了解一下最新的行业动态,多多去关注行业的未来发展趋势时代日新月异的在更替,这个行业的变化更快。 咳咳咳~回归今天的重点话题,我们究竟该如何学习Linux?学习Linux需要对一些命令比较熟悉,因为Linux命令有很多强大的功能,掌握了命令,Linux也差不多入门了。当然,Linux的命令有很多,记住也不是那么容易,学习命令不能靠死记硬背,要理解记忆,而且对于初学者来说,马上学习枯燥的命令确实很无趣,但是一旦学会就会爱不释手,非常喜欢。我建议大家学命令的时候可以通过一些小程序练手,一回生二回熟,慢慢的就掌握了。学习Linux可以从Rad hat去着手,这个是红帽公司出品,一直在更新的一个Linux系统,是小白的第一选择。Linux命令大全:https://www.linuxcool.com/ 1.从学生自身来说,很多的计算机系的学生都是大学之前迫于学业压力,都是大学之后才开始接触编程。而这之前,他们玩游戏,聊qq等等这些linux下不支持或者不完美的习惯他们养成很久,要改正很难。甚至在他们眼中,windows才是最好的操作系统。这让我一直不解。 2.从初学者到熟练使用linux需要相对较长的时间,而在一次次的遇到问题之后,很多人打消了继续使用的念头。即使现在的很多发行版已经在易用性上有了很大的提高。 3.很多大学生在大学玩的游戏居多都是Linux的,而windows才能给他们提供平台。 4.从学校来说。很多的学校都是在大三甚至大四才会开设linux相关课程,而且课程都很水。 5.学习C语言,注意是C语言就用Linux发行版学习。之前一直以为Linux和Windows差不多,但是学习了Linux基础入门之后才发现两种操作系统之间差距非常大。 Linux只是在硬件之上的内核和系统调用,就连我们在Windows里习以为常的图形界面都是Linux上的软件。在使用Linux的时候,我们都习惯于使用终端和命令行进行操作,而不是像Windows那样的图形界面里的鼠标键盘的共同操作。Windows的后台操作也是基于文件命令,譬如”cdm”命令界面。 总而言之,学习只有努力刻苦,学习编程更是如此,10000小时定理是我们的小目标,加油吧,少年们。

    时间:2020-04-17 关键词: Linux C语言 c

  • c编译器解惑篇,如何造就c编译器

    c编译器解惑篇,如何造就c编译器

    c编译器尤为重要,缺乏c编译器,很多应用将无法运行。此外,没有c编译器,很多系统同样无法正常运转。在很多朋友眼里,对c编译器充满疑惑,如c编译器是c语言编写的,那么第一个c编译器是如何而来呢?如果你对这个问题同样不太了解,不妨一起来看下哦。 当今几乎绝大部分实用的编译器/解释器(以下统称编译器)都是用C语言编写的。有一些语言比如Clojure,Jython等是基于JVM或者说是用Java实现的,IronPython等是基于.NET实现的。但是Java和C#等本身也要依靠C/C++来实现,等于是间接调用了C。所以衡量某种高级语言的可移植性其实就是在讨论ANSI/ISO C的移植性。C语言是很低级的语言,很多方面都近似于汇编语言,在《Intel 32位汇编语言程序设计》一书中,甚至介绍了手工把简单的C语言翻译成汇编的方法。对于编译器这种系统软件,用C语言来编写是很自然不过的,即使是像Python这样的高级语言依然在底层依赖于C语言。举Python的例子是因为Intel的黑客正在尝试让Python不需要操作系统就能运行——实际上是免去了BIOS上的一次性C代码。现在的学生,学过编译原理后,只要有点编程能力的都可以实现一个功能简单的类C语言编译器。可是问题来了,不知道你有没有想过,大家都用C语言或基于C语言的语言来写编译器,那么世界上第一个C语言编译器又是怎么编写的呢?这不是一个“鸡和蛋”的问题…… 让我们回顾一下C语言历史:1970年Tomphson和Ritchie在BCPL(一种解释型语言)的基础上开发了B语言,1973年又在B语言的基础上成功开发出了现在的C语言。 在C语言被用作系统编程语言之前,Tomphson也用过B语言编写过操作系统。可见在C语言实现以前,B语言已经可以投入实用了。因此第一个C语言编译器的原型完全可能是用B语言或者混合B语言与PDP汇编语言编写的。 可能有不少人知道,B语言的执行效率比较低,但是如果全部用汇编语言来编写,不仅开发周期长、维护难度大,更可怕的是失去了高级程序设计语言必需的移植性。给一张图让大家感受一下这的差别:  所以早期的C语言编译器就采取了一个取巧的办法:先用汇编语言编写一个C语言的一个子集的编译器,再通过这个子集去递推完成完整的C语言编译器。详细的过程如下: 先创造一个只有C语言最基本功能的子集,记作C0语言,C0语言已经足够简单了,可以直接用汇编语言编写出C0的编译器。依靠C0已有的功能,设计比C0复杂,但仍然不完整的C语言的又一个子集C1语言。 其中C0属于C1,C1属于C,用C0开发出C1语言的编译器。在C1的基础上设计C语言的又一个子集C2语言,C2语言比C1复杂,但是仍然不是完整的C语言,开发出C2语言的编译器……如此直到CN,CN已经足够强大了,这时候就足够开发出完整的C语言编译器的实现了。 至于这里的N是多少,这取决于你的目标语言(这里是C语言)的复杂程度和程序员的编程能力——简单地说,如果到了某个子集阶段,可以很方便地利用现有功能实现C语言时,那么你就找到N了。 下面的图说明了这个抽象过程: C语言 CN语言 …… C0语言 汇编语言 机器语言 那么这种大胆的子集简化的方法,是怎么实现的,又有什么理论依据呢? 先介绍一个概念,“自编译”Self-Compile,也就是对于某些具有明显自举性质的强类型(所谓强类型就是程序中的每个变量必须声明类型后才能使用,比如C语言,相反有些脚本语言则根本没有类型这一说法)编程语言,可以借助它们的一个有限小子集,通过有限次数的递推来实现对它们自身的表述,这样的语言有C、Pascal、Ada等等,至于为什么可以自编译,可以参见清华大学出版社的《编译原理》,书中实现了一个Pascal的子集的编译器。总之,已经有计算机科学家证明了,C语言理论上是可以通过上面说的CVM的方法实现完整的编译器的,那么实际上是怎样做到简化的呢? 这张图是不是有点熟悉?对了就是在讲虚拟机的时候见到过,不过这里是CVM(C Language Virtual Machine),每种语言都是在每个虚拟层上可以独立实现编译的,并且除了C语言外,每一层的输出都将作为下一层的输入(最后一层的输出就是应用程序了),这和滚雪球是一个道理。用手(汇编语言)把一小把雪结合在一起,一点点地滚下去就形成了一个大雪球,这大概就是所谓的0生1,1生C,C生万物吧? 下面是C99的关键字: auto enum restrict unsigned break extern return void case float short volatile char for signed while const goto sizeof _Bool continue if static _Complex default inline struct _Imaginary do int switch double long typedef else register union //共37个 仔细看看,其实其中有很多关键字是为了帮助编译器进行优化的,还有一些是用来限定变量、函数的作用域、链接性或者生存周期(函数没有)的,这些在编译器实现的早期根本不必加上,于是可以去掉auto, restrict, extern, volatile, const, sizeof, static, inline, register, typedef,这样就形成了C的子集,C3语言,C3语言的关键字如下: enum unsigned break return void case float short char for signed while goto _Bool continue if _Complex default struct _Imaginary do int switch double long else union //共27个 再想一想,发现C3中其实有很多类型和类型修饰符是没有必要一次性都加上去的,比如三种整型,只要实现int就行了,因此进一步去掉这些关键词,它们是:unsigned, float, short, char(char 是 int), signed, _Bool, _Complex, _Imaginary, long,这样就形成了我们的C2语言,C2语言关键字如下: enum break return void case for while goto continue if default struct do int switch double else union //共18个 继续思考,即使是只有18个关键字的C2语言,依然有很多高级的地方,比如基于基本数据类型的复合数据结构,另外我们的关键字表中是没有写运算符的,在C语言中的复合赋值运算符->、运算符的++、– 等过于灵活的表达方式此时也可以完全删除掉,因此可以去掉的关键字有:enum, struct, union,这样我们可以得到C1语言的关键字: break return void case for while goto continue if default do int switch double else //共15个 接近完美了,不过最后一步手笔自然要大一点。这个时候数组和指针也要去掉了,另外C1语言其实仍然有很大的冗杂度,比如控制循环和分支的都有多种表述方法,其实都可简化成一种,具体的来说,循环语句有while循环,do…while循环和for循环,只需要保留while循环就够了;分支语句又有if…{}, if…{}…else, if…{}…else if…, switch,这四种形式,它们都可以通过两个以上的if…{}来实现,因此只需要保留if,…{}就够了。可是再一想,所谓的分支和循环不过是条件跳转语句罢了,函数调用语句也不过是一个压栈和跳转语句罢了,因此只需要goto(未限制的goto)。因此大胆去掉所有结构化关键字,连函数也没有,得到的C0语言关键字如下: break void goto int double //共5个 这已经是简约的极致了。 只有5个关键字,已经完全可以用汇编语言快速的实现了。通过逆向分析我们还原了第一个C语言编译器的编写过程,也感受到了前辈科学家们的智慧和勤劳!我们都不过是巨人肩膀上的灰尘罢了!0生1,1生C,C生万物,实在巧妙! 以上便是此次小编带来的“c编译器”相关内容,希望大家对本文讲解的内容具备一定的认知。如果你喜欢本文,不妨持续关注我们网站哦,小编将于后期带来更多精彩内容。最后,十分感谢大家的阅读,have a nice day!

    时间:2020-03-09 关键词: 汇编 C语言 c编译器 指数

  • JAVA学习(12) JAVA调用C++

    package com.ui.test; public class test { public test() { } static { System.loadLibrary("jnitest"); } public native void testfuc(String instring); public static void main(String[] srgs) { test testx = new test(); testx.testfuc("hello"); } } #include "stdafx.h" #include static void JNICALL Test(JNIEnv *env, jobject arg, jstring instring) { const char *str = (const char *)env->GetStringUTFChars(instring, JNI_FALSE); ::MessageBoxA(0, str, "str", 0); env->ReleaseStringUTFChars(instring, str); } static const JNINativeMethod gMethods[] = { //定义批量注册的数组,是注册的关键部分 { "testfuc", "(Ljava/lang/String;)V", (void*)Test } // func2是在java中声明的native函数名,"()V"是函数的签名,可以通过javah获取。 }; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void *reserved) //这是JNI_OnLoad的声明,必须按照这样的方式声明 { ::MessageBoxA(0, "JNI_OnLoad", "str", 0); JNIEnv* env = NULL; //注册时在JNIEnv中实现的,所以必须首先获取它 jint result = -1; if (vm->GetEnv( (void**)&env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,一般使用1.4的版本 return -1; jclass clazz; static const char* const kClassName = "com/ui/test/test"; clazz =env->FindClass(kClassName); //这里可以找到要注册的类,前提是这个类已经加载到java虚拟机中。 这里说明,动态库和有native方法的类之间,没有任何对应关系。 if (clazz == NULL) { ::MessageBoxA(0, "JNI_OnLoad", kClassName, 0); return -1; } if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) //这里就是关键了,把本地函数和一个java类方法关联起来。不管之前是否关联过,一律把之前的替换掉! { printf("register native method failed!n"); return -1; } return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。 }

    时间:2020-01-08 关键词: C语言 java

  • 2年重写10年279万行代码……

    2年重写10年279万行代码……

    改变,做最好的软件2018年年底,华为网络金码奖颁奖典礼会场掌声雷动,看着台上我们团队的3名员工站立正中,举起象征着“码农”至高荣誉的奖杯,我在台下思绪万千。两年前,公司5G微波等新产品启动开发,工作量几近翻倍,为提高开发效率和质量,我们一边顶着巨大的交付压力,一边痛下决心,用全新理念和架构重写10年存量的279万行代码,将其优化为90万行。这就好比汽车一边高速行驶,一边“换轮胎”,难度非常大。过程中虽充满了煎熬和不被认可,但当看到一个又一个软件精英在团队涌现,交付的代码在多个维度高于华为英国安全认证中心的要求,产品及时、安全、可信地交到客户手中,我觉得一切坚持都值了。革自己的命,义无反顾踏上架构重构之路我们是公司传送网软件平台开发部门,类似于做手机操作系统的部门,不过我们开发的是波分、微波等网络通信产品的软件。手机操作系统让大家享受智能手机的各种功能,离不开我们开发的软件。作为平台开发部门,我们总共支撑十余款传送产品的软件开发工作,交付压力一直比较大。时间来到2016年,产品线开始集中开发5G微波等六七个新产品,而且集中在一两年内推出来并支持测试和商用,时间紧,任务重。面对几乎翻倍的工作量,如果沿用传统的基线效率来开发,可以想象大家将一直处于疲于奔命的状态。其实,过去我们在组织运作、工程能力等效率提升方面做了大量的努力,收效还不错,但也感觉进入了瓶颈期。原因很简单,问题的根源在我们的传统软件架构上。传统软件架构是建造软件大楼的“基座”和“框架”,已经用了十来年,随着环境的变化以及新技术、新功能特性的引入,架构难免腐化,就像一部用了很久的手机,开始变得慢、“笨重”,问题还多,再怎么修修补补都无济于事,除非做个大手术。5G微波产品的爆发式启动开发,更将我们的开发效率问题逼上了梁山。不破不立,2016年年中,传送产品线的上级主管引进了一名海外研究所的专家,期望我们与他合作,在架构上做些探索,我作为团队新上岗的LM(Line Manager,资源线主管)承接了该工作。经过与这位专家深入讨论,发现他的业务抽象建模思想完全可以解决我们跨产品、跨芯片重用的难题。用一个不太恰当的比喻,他的架构和我们传统架构相比,类似于“活字印刷”与“刻版印刷”之别。以往我们每支持一款新的芯片都需要重建一套软件模型,但他帮助我们构建一套统一的、可灵活拼装的通用模型,可以极大提升软硬件解耦和重用能力。用上这个架构,我们可以实现“一次开发,多次使用”,而不是之前的“使用一次,开发一次”,效率大大提高。我们成立了一个技术项目组,花了小半年时间仔细验证,最终确认抽象模型与实际业务匹配,方向是可行的。但摆在面前的挑战非常大,需要使用新的架构方法重写原来近300万行代码。对于一个80余人的团队来说,除去正常的产品需求开发,还需要额外完成架构重构,这几乎是不可能完成的任务。“太冒险了,万一完成不了5G微波的交付,影响太大了。”“使用老架构,虽然效率低、问题多,勉强也能把产品推出来。”“老架构大家都在骂,我们能不能做个软件,未来6到8年不被人骂?”……经过多次研讨, 大家逐渐统一了意见:研发应更关注长期,不能因为眼前的一点风险而放弃对未来的追求。但风险也不能不顾,为此,我们制定了一份详尽的计划,并且邀请了产品线3位软件牛人加入,再加上本部门五六位软件高手的投入,组成了一个10余人的小团队探路,准备杀出一条血路,革自己的命。写最优秀的代码,不“爽”不休架构重构就像把老房子推倒重建,代码就是高楼大厦的一砖一瓦,没有高质量的代码,任何好的架构都会演变成一个坏的架构。在重写近300万行代码之前,大家对什么是最优秀的代码进行了讨论。写代码就像是艺术创作,优秀的原则很难形成统一标准,而软件总工程师申力华常常挂在嘴边的“爽”字,成为我们对代码的一致追求。“爽”是什么概念?现在回头来看,其实就是“Clean Code”,代码要简洁、易阅读、易重用、易扩展、易测试、高可靠。其次,大家一致认可函数要短小、文件要小,函数深度不能过深、文件不要网状依赖……要写出“爽”的代码,第一个面临的就是编程语言的选择。C++在公司已经用了十几年,我们都清楚C++的复杂,都对C++代码中经常遇到的内存管理问题深恶痛绝。几个技术专家在工作之外,深入学习和实践了C++11,深知C++11在编码效率和安全性上具有天然的优势,而且C++11已经得到业界的认可。但C++11在华为产品中无应用经验,无支撑工具链,有人认为最好等配套工具成熟后,再切换编程语言。在没有经验和工具支撑的情况下,谁敢去脱一层皮?但最后大家还是统一了认识,不脱皮,何来脱胎换骨!编程语言应该切换成C++11,在使用中催熟工具。为了让我们的架构约束得到落实,为了让代码简洁,我们部署了诸多代码门禁,不符合要求的“砖瓦”连“施工场地”都进入不了。我们参考优秀开源代码库的要求,制定严格的标准(如函数最大圈复杂度不能超过5,函数代码、函数行数不能超过30行),超出门禁标准的代码不允许入库。严苛的门禁对固有的编码习惯形成巨大的冲击,所有代码都需要白盒测试(注:在知道目标功能的前提下采用的一种测试方法,与黑箱测试法不同,白箱测试法关注程序在设计盒定义方面的缺陷与错误,故要求对程序代码本身要有较详尽的了解)覆盖,因此带来了成倍的编码工作量,以至代码一度堆积到上万行无法上库。一时间,团队中“认清现实、杜绝理想主义”的呼声越来越高。我们进行了激烈的辩论,虽然对绝大多数人来说,蜕变是一个痛苦的过程,但是大家也都认可好的代码能够带来质量和效率提升的价值。因此,我们解剖了从编码到上库的所有环节,有什么问题就解决什么问题。比如支撑工具PC-Lint不支持,我们就找到替代的开源工具Clang-tidy;语言和架构难,我们通过牛人带牛人,让专家手把手教;门禁严格,我们提升门禁执行效率、本地部署门禁检查项,在同等的时间内可以多次执行门禁,在实战中逐渐改变编程习惯。在追求极致的过程中,团队成员互相检视,时刻切磋,不留情面地驳回一切不“爽”的代码。部门一名骨干成员兴冲冲地加入到该重构项目,由于是公认的软件高手,他第一次信心满满地提交了代码,但被评审人员无情地驳回了:“你这个设计不够简洁,还需要考虑异步场景的扩展性。”通过不断修改、提交,一共被驳回了8次。在开始的几次被驳回时,他虽然内心非常不服气,但经过讨论和深挖,发现确实可以找到更好的设计,最终将原来需要500行代码实现的功能现在使用200行即可实现。团队屡“驳”屡战,不“爽”不休,每一处代码的设计与编码只要觉得还可以更好,就持续优化,直至无可挑剔。新技术的引入,更好看的代码,高手间切磋带来的快感,让大家又找回了编程的乐趣。一群软件专家在自己的“独立王国”中,恰同学少年,挥斥方遒。 进度与质量重压下的选择技术问题虽得到解决,然而更大的压力来自外部。交付过程中进行软件架构优化无异于飞行途中换引擎,速度必然受到影响。架构优化的优势要两三年才能凸显出来,当下感知没有那么明显,还因为人力限制等因素,给人感觉“你飞得更慢了”,质疑声不断。内部研发过程中因为进度的延迟,多次受到产品线的投诉。产品线不断地发出预警邮件、不断地在各级会议通报风险,产品部和我们团队都承受着巨大的压力。2017年年底,我们迎来了最艰难的时刻。5G微波正式进入预商用阶段,距离某大T运营商客户测试仅有三个月,我们还有几万行代码没写出来。产品线判断按时完成任务的风险巨大,投诉接踵而至。按照以前的方法,面对一个有进度压力的需求,复制一个代码,稍作修改即提交是最方便快捷的,虽然这种方式可能在后期问题会比较多,但不会出现大面积延迟交付的情况。但当时我们的架构已经发生了天翻地覆的变化,要求和标准都有了更高的基线,开发进度受限。是坚持对代码的追求,还是降低标准以满足眼前的交付进度?团队内部一直有两种不同声音,每隔半个月,双方就“房间具体要装修到什么程度”进行讨论博弈。“保守派”认为,大堂搞豪华点就行了,其他的简单装修,这样可以快速交付,面对产品线的压力小一些;“激进派”坚持高标准,所有房间的装修必须用最好的方案、最好的材料, 要做良心工程。从我的角度来说,对代码最高的要求是我们的初心,必须坚守,质量差的材料也可以完成房屋装修,但寿命不长,还时常漏水漏电、墙皮脱落,我们宁愿暂时苦一点,也要保证长期不出问题。面对外部压力,我们也不是一个人在战斗,时任产品部部长王春钿、流程负责人杨曦给大家尽可能争取更宽松的外部环境,并争取到产品线管理层的支持,不断给大家释放压力。同时,我们预定多个会议室封闭开发,大家把手机统一放在“停机坪”上,全身心投入。那段时间大家一个星期不回家是很正常的事情,累了困了就在公司打地铺。坚持高质量代码交付,让我们实现了以质量换进度。以前还是老架构时,写代码时间短,可能两个月就写完了,但后续问题多,解决问题也要一个半月,而且再扩展新功能很麻烦,投入大;如今虽然前期写代码要两个半月,但后续问题少,解决问题只要半个月,扩展新功能很容易、投入小。经过一段时间的紧张开发,某大T运营商客户测试的几万行代码也按时交付了,而且质量比之前更高。从量变到质变,奇迹发生了就在我们没日没夜开发时,内部测试发现了某芯片存有一个致命的设计问题,可能需要改片。面临数百万美元的改片费用,产品线认为芯片早期验证存在疏漏。一纸通报批评落到了我头上,整个团队笼罩在不被信任、压力无处诉说的阴霾之中。当所有人认为只有改片这条路时,我们没有放弃。几名专家通过连续一周的论证,找到了一条可以用软件来解决芯片缺陷的方案。再经过一周的编码与验证工作,在某天的凌晨三点,验证项全部通过,大家一片欢呼,喜极而泣,不仅因为节省了数百万美元,更是因为我们通过智慧,解决了一个原以为不可能解决的问题。这个致命问题的解决,只是我们这两年来遇到的众多问题中的一个,类似的挑战和压力无处不在。新架构、新技术与现有人员能力的不匹配带来的挫败感,产品线的不断投诉和质疑带来的无助感,有时让大家产生动摇。有一天,一个关系要好的PL(Project Leader,项目主管)骨干向我提交了辞职信,正焦头烂额的我犹如当头一棒。他说,我很清楚事情的价值,但实在太辛苦了,还不被认可。静下心来,我和他掏心掏肺地聊了很久,他理解当下兄弟们的难,现在正是缺人之际,答应等过了这段时间再走。那段时间对我来说,压力非常大,绩效受到影响,连续两年拿了B,要好的朋友都劝我换个地方,但我还是想把事情做完,虽然不被理解,也无处诉说。有个周五晚上部门组织看电影,当天刚被产品线投诉,我心情非常压抑,电影很欢快,欢笑声此起彼伏,但我完全没看进去,坐在昏暗的影院里,一个三十多岁的大男人,眼泪止不住地流下来。中途走出影院,外面下着淅淅沥沥的小雨,我拦了辆出租车准备回家,但想到公司肯定还有同事在加班,还有很多问题需要解决,毅然让司机调头回到公司。使命还未完成,我们还须坚持。项目进行到中后期时,部门60%的人员都加入到了新架构重构工作中来。随着开发活动的深入,大家的能力也逐步得到提升,从量变到质变,奇迹就此发生。大家突然发现,门禁失败次数、被Committer(代码提交者)驳回的MR(提交请求)个数越来越少;白盒测试覆盖率日渐提升,迭代期间缺陷密度远低于历史水平;代码直观漂亮,书写行云流水。新架构、新代码带来的好处终于开始显现。平台代码总量实现了从297万行到90万行的“瘦身”,我们的开发效率得到极大提升,开发相似的单板要修改的代码量比以往减少一半以上,更少的人力便可支撑现有业务。至此,交付逐步赶上进度,团队也迎来了5G微波产品的成功,5G微波和波分新产品高质量地通过120场次的客户测试。我们还意外发现,我们开发的新代码在代码重复度、函数圈复杂度等多个关键指标上优于华为英国安全认证中心的要求。拨开云雾见月明,软件“场”已经形成两年多来,随着一群有追求的同事敲出一行行优秀的代码,团队形成了一种软件“场”。大家已经形成一种共识,“我不愿意成为破坏软件架构和好代码的第一人”。当时跟我提离职的PL最终也留了下来,我们清楚地知道,所做之事,值得一生付出。如今公司越来越重视软件工程能力提升,计划用5年时间,在ICT基础设施领域实现为客户打造可信的高质量产品的目标。今年年初,公司总裁任正非在《全面提升软件工程能力与实践,打造可信的高质量产品——致全体员工的一封信》中提到,“我们要从最基础的编码质量做起,视高质量代码为尊严和个人声誉”,对此我感触良多。高质量代码就是我们心中的信仰。迎着朝阳前进,做最好的软件,我们一直在路上。希望每个人都有勇气做正确的事,有决绝的信念坚持到底,用代码筑起心中的殿堂。

    时间:2019-12-11 关键词: 华为 C语言 代码

  • 工程师分享:战斗在0与1的世界,将代码写到极致

    工程师分享:战斗在0与1的世界,将代码写到极致

    来源:华为人,二次来源:今日头条@暴走通信,作者:白嗣健大二时,在别人还痴迷于打魔兽的时候,我凭一腔好奇与热情做出了人生中的第一款赛车游戏。与代码结缘,让我看到了0与1世界所隐藏的神秘力量。曾经,我以为软件工程就是写出好代码,后来才慢慢意识到:好代码仅仅是软件工程中的一环,还有很多细节中的魔鬼。代码结缘,痴迷做游戏我与代码结缘要从游戏说起。大学时代,大多数男孩都有一颗爱打游戏的心。当看着室友们飞速地敲打着键盘和鼠标、显示器上呈现出各种炫酷的画面时,那一刻,我真的非常好奇:软件真是个神奇的好东西!往后三个月,我一直都致力于编出一款赛车小游戏,那时才第一次发现:软件真的有非常强大的创造性。那一个个字母组合在一起成为映入眼帘的左/右拐、加速/刹车,仿佛是在盖一座房子,从设计到施工,一砖一瓦地构造自己的想象。后来,我开始关注2D/3D游戏,开启了“各种搜”模式:游戏类库、引擎代码、各种社区/论坛中“摸爬滚打”,学习并开始PK。做游戏,让我对软件的热爱不断升级。在大学毕业论文答辩时,因为编程,我的成绩成功晋级全校前十,那种喜悦和激动难以言表。来华为前,我在一家全球性的软件设计与咨询企业工作,这里工程师文化浓厚,追求极限编程,敏捷理念、开源思维、Tough精神、极客追求、Idea变现……使我对软件的认识更加深入,能力不断提升,而这些后来一直贯穿在我的职场生涯中。工作9年后,2016年底,我来到华为西安研究所。框架重构?你敢,我们就敢!软件开发人员都知道重构的意义,当一个系统修改代码成本太高的时候,就不得不面临重构。初到华为,我感受到一种明显不同的氛围。在华为,更加注重按照既定的计划完成交付、使命必达,遵循计划大于响应变化,“如何将代码写到极致”的问题似乎碰撞不多。对于一个代码发烧友,我的技术洁癖的劲头又上来了。我发现所在项目的软件架构版本、语言版本都很老,由于没有测试框架保护,新功能经常会引入问题,重构更是无从下手;即使修改一行代码也需要全量编译,每次编译时间高达5 分钟;无法本地调试,由于环境代码是编译过的,所以也无法远程调试,调试环境是公用的,使用需要排队,有时为了验证特性需要等很久。那时,我就在想:“如果能解决这些问题,那代码开发调试和自验证的效率都会大幅提升,也会节省很多开发环境。”为了让项目组更加信任自己的想法,我决定先完成新的代码框架开发。每天下班后都会投入其中,两个月后,我终于有了向大家“Show Case”(效果演示)的勇气和实力。这个大胆的尝试得到了整个项目组的认可。在一个月后的新项目中,它迎来了真正的战场。那天项目主管找到我:“嗣健,这次新项目,你敢不敢用新的框架彻底替换老的React 框架?”——这个问题,令人惊喜,同时又极具挑战!我问同组的兄弟们:“敢不敢?”“你敢,我们就敢!”——这种信任,令人惊喜,同时又力量倍增!于是,说干就干!当然,成功的路哪能一帆风顺。就在完成替换的第一个月里,各种安全问题、历史埋藏的大坑小坑一个个都蹦了出来。在那一个月里,在压力中我们憋着一股劲儿,尽管有一些质疑声,但我们依然坚信:问题必然会收敛!令我们意外的是,这个收敛竟来得如此之快——在完成替换的第二个月,整个系统逐渐趋于稳定。在后续的日子里,项目组的兄弟姐妹们在自己的桌面云上就可以开发调试,从曾经的“先前端再后端”到现在的“前后端并行开发”,从曾经的“测试一个场景重出一个版本”到现在的“只需替换一个组件就是一个场景”,这也为本地模拟更复杂的测试场景带来了可能。代码重构中,对数据库差异层和通用逻辑层进行分层解耦,精简多数据库场景下生成动态SQL(Structured Query Language,结构化查询语言)的业务代码,重构后的代码量从96K下降到37K,大大降低了代码缺陷率及维护成本。也许,一个勇敢的小改变,真的可以带来明显的质量提升及效率提升。布下“代码检视”阵,战场磨练出高手某天,团队的一次代码检视会议上。“小张,你提交的上帝类(被多个特性所引用的类)代码不符合公司的编码规范,竟然有1000行!这必须得重构呀。” 团队主管说。小张辩解道:“这个类是老代码,又不是我写的;况且之前也没有评估这个工作量,我估计这个迭代版本重构来不及。”“小张,对于修改的类要有责任人;如果时间不允许的话,我们有专门的重构管道,这个迭代做不完可以排到下个迭代来完成,先给你提个问题单,跟踪起来。好,这个类有哪些Bad Smell,大家一起来说说。”团队主管启发大家。过大的方法、基本类型偏执、发散式修改、无效的注释……大家你一言,我一语。在一个个“铁证”面前,小张无言以对,说:“回去我一定好好学习一下重构,以后再猛怼你们。”在实践中,我们总结出了提升检视效率的三个步骤:第一,Show Case,你做了什么;第二,讲理念,怎么设计;第三,看小步提交代码,从逻辑、风格、规范等方面全方位去找问题。三步法逐渐得到固化,我们的检视从原先的一个小时缩减到了半个小时。在接下来的检视中,你会发现那些平日里沉默腼腆的同事在这里成了主角。某天,我的徒弟小顾组织代码检视会议。“小顾,这次的功能写得很简洁哈,再也不像你刚入职时的Ctrl+C(拷贝)、Ctrl+V(粘贴)啦!”小顾不好意思地说:“客户提出,某些功能上华为的代码量太庞大了。作为研发人员,功能实现是一方面,代码本身的质量也非常重要,多想一步,代码其实可以写得更好。”“小顾,你的代码测试用例还是自动生成的吗?可不要只为覆盖率啊……哈哈哈……”大家笑了起来。“当然不是自动生成的,那可都是我一行一行码出来的。我们先来检视测试用例,之前项目组的防护理念测试专项赋能中,我可是每场都记满了笔记啊。来,大家一起来看看……”小顾说。为了快速达成代码的高覆盖率测试,人们却往往不自觉地加速了测试代码的腐化。我们也被测试覆盖率困扰了很久,一直在覆盖率与高质量测试之间纠结……这让我想起半年前和主管李哥的一番对话。那天李哥问我:“我们的测试覆盖率那么高,为什么问题单数降不下来?”“我们的测试用例大部分是自动生成的,没有真正从业务逻辑出发,所以这些测试都是无效测试。要想改变现状,就必须让大家写出真正有效的测试,一方面需要给大家赋能、如何写出高质量测试,另一方面我们要摒弃任务式追求覆盖率的习惯。”我答。李哥认可了我的观点。那天,我们聊了很久,具体讨论几个项目团队的现状、分阶段实施的操作细节,以及由此带来的成本投入及风险应对。一周后,各个团队就开始行动起来,踏踏实实地落实有效测试。这些是我们代码检视过程中经常发生的情形。在团队中代码是集体所有制,当你在修改其他人代码的时候,那你就成了它最新的主人,慢慢地就形成了代码主人翁意识。我们约定:代码检视会上没有领导、没有权威,大家摆开阵势后,有的就是畅所欲言、激烈PK,工程师文化也逐渐浓郁起来。三大“武器”,写出高质量代码在激烈的检视对阵辩论时,我们还会采用好用、适用而且强有力的“武器”,那就是:“编码规范”“集体检视”“代码坏味道”。“编码规范”,就如同开车上路时要遵守交通法规,红灯停、绿灯行,违规就要被严罚。在公司级编码规范基础上,我们部门也推出了自己的编码指南,这就好比在到达目的地的路上提供了导航,能够避免司机少走弯路。而“集体检视”就如同全员都是交通警察。再好的规范都需要有人监督执行,它能互相监督、信息共享,当司机出现违反交通法规的行为时,他能够第一时间被其他司机提醒,及时改正错误驾驶动作。发现、提醒问题最多的那个人很快就成为团队的法规领袖。“代码坏味道”,顾名思义,比如说当食物快要腐败时,往往会散发出一些难闻的味道,通过辨别,我们可以针对不同的坏味道做出相应的措施。代码的道理也一样,如果一段代码不稳定或者有一些潜在问题,那么代码往往会包含一些明显的痕迹。我们会通过识别代码中的坏味道来精确实施重构,从而避免代码的持续腐化。通过采用业界优秀实践方法和实战磨砺,大家逐渐明白了什么是高质量的代码,什么是需要坚持的,什么是需要摒弃的。在这个过程中,大家的能力潜移默化地得到了提升,每个人的知识库也不断丰满,就像小顾,现在他既是Web 前端的一把好手,同时也是大家公认的DBA(Database Administrator,数据库管理员)专家。也许你不难发现,大多数的软件开发工程师都像金庸小说里的高手,低调、话不多却身怀绝技,他们的战场在自己手里的0与1世界中,在“你的问题不闭环就休想提交”的“执拗”中。在我看来,写好代码就是在争论与吵架中,让每个人的思维得到碰撞,让问题得到最完满的解决,它不需要东方美女式的一致标准,只需制定出真正适合团队的一套运作共识。变革不是斯诺克,岂能单打独斗?可信,它不是一蹴而就的事,更不是一个人就可以扭转乾坤的斯诺克。就像一波浪潮,它需要万千水分子共同发力。各类可信工作开始运作后,组织的支持就变得格外重要了。为了让可信有效落地,部门预留了10%~20%的可用于重构的工作量。作为架构师,我双周会组织一次例会,大家将近期问题抛出来,识别出其中的共性问题;同时,软件总工程师也会例行获取团队的问题,当然也包含一些好的创意点子。这些问题或点子经过评审后,大部分任务会在部门季度审视中落入管道,而小部分任务也会转入“极客连”进行闭环。我们会将工作量进行分解,这样一个任务通常一周投入3个人就可以实现,而参与者则可能获得“极客装备”的奖励,在后续的评优考核中优先考虑。而为了进一步有效减少共性问题,我们还建立了部门CBB(公共组件),通过开源运作,带领我们模块架构师组的成员孵化出5个CBB,目前均已经投入使用并取得了不错的效果。经过持续的实践积累与反复迭代,我们的交付结果及人员能力都得到了较明显提升,支撑多个下游产品达成商用挑战目标,并获得了公共开发部的金质奖。我们把在可信落地的一些实际做法进行了总结,由我贡献思想、部门HR 刘姐协助结构化,《可信落地的具体实践探索》一书正式出炉,其中的方法和案例也很快在多个团队展开推广与学习,帮助更多的团队提升可信落地效果,也受邀在深圳、西安等多个部门进行过分享。写在最后的一点分享有小伙伴问我:“如何提升自己的软件技能?”我想说的是,要想发挥编码人员的最大潜能,个人努力最重要,但还需要成长的土壤:第一,信任是对一个人最大的激励,一句“相信你!”胜过许多华而不实的赞许,而它也是一个组织的基础,与其防着他做,不如放手让他做。不要惧怕未知的风险,不要担心挑战的艰巨,坚持不懈、聚沙成塔。第二,每个人都是平等的,每个人都可以充分发表自己的观点,不要在团队中刻意树立技术权威,过于相信权威对团队、对个人来说都会是有弊无利的。不知你是否听过“飞轮效应”——为了使静止的飞轮转动起来,一开始我们必须使很大的力气,一圈一圈反复地推,每转一圈都很费力,但是每一圈的努力都不会白费,飞轮会转动得越来越快。当达到一个很高的速度后,飞轮所具有的动量和动能就会很大。人总是习惯于惯性而不是主动改变,可信变革正处于飞轮效应的启动阶段,我们会遇到很多的困难,需要我们聚集合力,用很大的力量才能让它飞速运转起来。努力是会被看到的,付出是会被感受到的。放手去干,我们终将看到不一样的改变。

    时间:2019-12-11 关键词: 华为 C语言

  • PHP入门(5) C++和PHP二进制传输

    C++需要实现PHP端的:bin2Hex函数,PHP通过这种类型的字符串调用:pack转换成PHP能识别的2进制数据。 C++需要做的是实现一个bin2hex,其实只是把c++读取的2进制数据当成byte数组,把每一位转换成16进制字符串就可以了。Qt中使用sprintf无法限制2位长度,因此sprintf之后判断长度为8则截取最后3个字符串,包含了/0终止符 #ifndef PHPCLASS_H #define PHPCLASS_H #include class PhpClass : public QObject { Q_OBJECT public: explicit PhpClass(QObject *parent = 0); //字节流转换为十六进制字符串的另一种实现方式 void Bin2Hex( const char *sSrc,QString& ret, int nSrcLen ); //十六进制字符串转换为字节流 void Hex2Bin( char* source, QByteArray& ret, int sourceLen); signals: public slots: }; #endif // PHPCLASS_H PhpClass::PhpClass(QObject *parent) : QObject(parent) { } void PhpClass::Bin2Hex( const char *sSrc,QString& ret, int nSrcLen ) { int i; char szTmp[3]; for( i = 0; i < nSrcLen; i++ ) { sprintf( szTmp, "%02X", (unsigned char) sSrc[i] ); ret.append(szTmp); } return ; } void PhpClass::Hex2Bin( char* source, QByteArray& ret, int sourceLen) { int i; unsigned char highByte, lowByte; for (i = 0; i < sourceLen; i += 2) { highByte = toupper(source[i]); lowByte = toupper(source[i + 1]); if (highByte > 0x39) highByte -= 0x37; else highByte -= 0x30; if (lowByte > 0x39) lowByte -= 0x37; else lowByte -= 0x30; ret.push_back((highByte

    时间:2019-11-25 关键词: C语言 php

  • Subdirectory 【Introduction to Linux basics】

    点击即可查阅~ 【Linux基础入门】 硬件知识:看懂原理图、通信协议、芯片手册 怎么看原理图之 GPIO 和门电路 怎么看原理图之协议类接口之 UART 怎么看原理图之协议类接口之 I2C 怎么看原理图之协议类接口之 SPI 怎么看原理图之协议类接口之 NAND Flash 怎么看原理图之协议类接口之 LCD 怎么看原理图之内存类接口 裸机开发:环境搭建 刚接触开发板之接口接线工具 刚接触开发板之烧写裸板程序 刚接触开发板之重烧整个系统 刚接触开发板之使用 vmwae 和预先做好的 ubuntu 刚接触开发板之u-boot, kernel打补丁编译 刚接触开发板之内核打补丁编译使用及建 sourceinsight 工程 刚接触开发板之制作根文件系统及初试驱动 在 TQ2440,MINI2440 上搭建视频所用系统 win7 下不能使用 dnw 烧写的替代方法 裸机开发:裸机程序开发 环境搭建及工具、概念介绍 GPIO实验 存储管理器实验 MMU实验 NAND FLASH 控制器 中断控制器 系统时钟和定时器 LCD实验 bootloader的学习 从0开始自己写 bootloader u-boot 分析之编译体验 u-boot 分析之 Makefile 结构分析 u-boot 分析之源码框架分析 u-boot分析之源码第1阶段 u-boot分析之源码第2阶段 u-boot 分析之 u-boot 命令实现 u-boot 分析_uboot 启动内核 linux内核的学习 内核启动流程分析 内核启动 根文件系统学习 根文件系统分析 待定 - 毕业班第 2 课第 1 节_移植最新 u-boot 之初试 - 毕业班第 2 课第 2.1 节_移植最新 u-boot 之分析启动过程之概述 - 毕业班第 2 课第 2.2 节_移植最新 u-boot 之分析启动过程之内存分布 - 毕业班第 2 课第 2.3 节_移植最新 u-boot 之分析启动过程之重定位 - 毕业班第 2 课第 3.1 节移植最新 u-boot 之修改代码之建新板_时钟 - 毕业班第 2 课第 3.2 节_移植最新 u-boot 之修改代码支持 NAND 启动 - 毕业班第 2 课第 3.3 节_移植最新 u-boot 之修改代码支持 NorFlash - 毕业班第 2 课第 3.4 节_移植最新 u-boot 之修改代码支持 NandFlash - 毕业班第 2 课第 3.5 节_移植最新 u-boot 之修改代码支持 DM9000 网卡 - 毕业班第 2 课第 4.1 节_移植最新 u-boot 之裁剪和修改默认参数 - 毕业班第 2 课第 4.2 节_移植最新 u-boot 支持烧写 yaffs 映象及制作补丁 - - 毕业班第 3 课第 1 节_移植 3.4.2 内核之框架介绍及简单修改 - 毕业班第 3 课第 2 节_移植 3.4.2 内核之修改分区及制作根文件系统 - 毕业班第 3 课第 3 节_移植 3.4.2 内核之支持 yaffs 文件系统 - 毕业班第 3 课第 4 节_移植 3.4.2 内核之裁剪及 ECC 简介及制作补丁 - 毕业班第 4 课第 1 节_移植驱动到 3.4.2 内核之 DM9000C

    时间:2019-11-21 关键词: Linux C语言

  • TIOBE编程语言2019年11月榜单:C与Java伯仲之间

    TIOBE编程语言2019年11月榜单:C与Java伯仲之间

    TIOBE公布了11月份编程语言排行榜。 本月前20名中有一些有趣的现象,先看看榜单: 首先,C现在非常接近Java,排在Java后指数仅差0.2%,预计年底之前C会再次冲上第一位;C++与Python已经连续几个月指数相差保持在大约3%、4%这个范围了,本月仍是Python占了上风,排在第三。 前10名的位置一直在发生变化,两个月前SQL入围,上个月是Objective-C,本月榜单上则由Swift挤进了前10,同时它与落后一位的Ruby指数差距达到 0.4%,在这样一个位置范围内,0.4%的差距不算小,预计Swift至少可以在接下来几个月内保持在前10的位置。 但是另一方面,Ruby本月的增长也不容小觑,目前它排在11位,但是去年同期它排在16位,可以看到榜单中标注了两个绿色的上涨箭头。所以关于下个月第10位的争夺,估计也不好说。 20名内同样引人关注的还有几个语言,首先是Go同比去年从第10降到了20位,目前指数0.853%;接着可以看到Groovy和D这两个似乎比较小众的语言反而同比去年都有较大的增长:Groovy从25位升到14,D从23升到18。 20名外,Rust在一个月内排名从34上升到了25,创下了历史新高。想必这与微软和AWS等巨头对外公开支持Rust的消息强相关,如果关于Rust的积极消息持续出现,那么它应该很快会进入前20名。 下边接着来看看20名后的排位: 21-50名: 值得注意的是,TIOBE指数并不代表语言的好坏,开发者可以使用该榜单检查自身的编程技能是否需要更新,或者在开始构建新软件时对某一语言做出选择。 详细榜单信息可以查看 TIOBE官网。

    时间:2019-11-06 关键词: C语言 编程语言 tiobe

  • General List

    点击即可查阅~ 01、【终身学习 生活化】 2017.7书单 2017.6书单 脱不花:关于时间管理的几个想法 春申门下三千客,小杜城南五尺天 100个句子直刷7000单词 另一个林则徐 02、【终身学习 职业化】 CMD一键获取cpu信息 有道云笔记Markdown笔记添加图片 笔算开方 有道云markdown编辑代码前后高亮 CMD一键获取 所有连接过的WIFI密码 windows无法连接到group policy client服务.此问题阻止标准用户登陆系统 100个句子直刷7000单词 Markdown添加空格效果 CodeBlocks的常用快捷键 03、【基于S3C2440 嵌入式Linux开发】 -Linux番外篇 -Linux基础篇 - [Linux驱动篇] - [Linux项目篇] 04、【问题集合】 ln 无法创建符号链接asm 只读文件系统 warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 64位系统结构体对齐访问段错误 (.ARM.exidx+0x0): undefined reference to `__aeabi_unwind_cpp_pr0’错误 shell脚本中cd命令无效的解决方案 Error: no such instruction: `ldr sp,=4096’ 如何解决warning: no newline at end of file? makefile:4: * missing separator. Stop. 05、【C语言基础】 C语言入门学习 06、【C语言高级编程】 基于VC6.0恶搞小程序 C语言和内存初步框架了解 小谈指针 C语言位操作 典型C内存空间分布图 谈谈#define xxxx(x,y) x##y 谈谈自定义头文件 深入谈谈整型、浮点型在内存中的存储方式 07、【Linux基础】 VMware虚拟机中安装Linux系统 VMware虚拟机安装VMware Tools Makefile中make几个基本操作 谈谈#!/bin/sh 谈谈linux_init命令 08、【Linux高级编程】 unbuntu16.04打造高逼格桌面 利用Oh-My-Zsh打造你的超级终端 安装挂科-guake 09、【OpenCV入门】 OpenCV入门学习 10、【工具类】 UltraEdit64位破解版绿色版下载及激活步骤 推荐一款非常好看notepad++比主题和字体 美化CodeBlocks的主题和字体 番茄工作法 计划表格式 10、【大学课程】 机构自由度的计算例子

    时间:2019-10-08 关键词: 编程 C语言

  • 生成so库供Java调用

    需求 在上次完成的gpio控制中,把c文件编译成一个so库,供java程序调用. 准备工作 首先在网上查找了大量的资料,然后一步一步的按照网上的教程在linux平台下面走了一边,包括.java文件编译成.class文件,然后生成.h文件,然后用c语言实现该方法即可.大概流程就是这样,觉得很简单.下午往android代码里面添加的时候发现很多问题.下面把详细步骤记录下来. 步骤 平台:展讯的7715,安卓7.0 java代码很简单,直接调用一个方法即可.用的还是HelloWorld示例. System.loadLibrary(“HelloWorld”); class HelloWorld { static { System.loadLibrary("HelloWorld"); } private native void greeting(); public static void main(String[] args) { new HelloWorld().greeting(); } } 编译生成.class文件,然后通过javah HelloWorld生成.h文件,然后编写.c文件包含.h文件并实现.h文件里面的方法. #include "HelloWorld.h" #include #include #include #include #include #define DEV_IOC_MAGIC '0xee' //定义幻数 #define DEV_IOCPRINT _IO(DEV_IOC_MAGIC, 1) #define DEV_IO_HIGH _IO(DEV_IOC_MAGIC, 2) #define DEV_IO_LOW _IO(DEV_IOC_MAGIC, 3) #define DEV_IOC_MAXNR 3 JNIEXPORT void JNICALL Java_HelloWorld_greeting (JNIEnv *env, jobject obj) { printf("Hello World!"); int cmd; int fd; int choice = 0; fd = open("/dev/dsx", O_RDWR); if(fd < 0) printf("ni haishi dabukai ya !n"); /*命令1:打印串口信息*/ while(1) { int i=0; printf("Please input choice: n 1: DEV_IOCPRINT; n 2: DEV_IO_HIGHn 3: DEV_IO_LOW n 4: EXIT n"); scanf("%d", &choice); if(choice == 4) { printf("gpio test exitn"); break; } switch(choice) { case 1: { printf("n"); cmd = DEV_IOCPRINT; if (ioctl(fd, cmd) < 0) { printf("Call cmd MEMDEV_IOCPRINT failn"); } break; } case 2: { printf("n"); cmd = DEV_IO_HIGH; if (ioctl(fd, cmd) < 0) { printf("Call cmd DEV_IO_HIGH failn"); } break; } case 3: { printf("n"); cmd = DEV_IO_LOW; if (ioctl(fd, cmd) < 0) { printf("Call cmd DEV_IO_LOW failn"); } break; } default: break; } printf("nn===================================n"); } } 直接把原来的代码里面的main函数放到了Java_HelloWorld_greeting里面. 本以为这样就可以了,但是编译生成的so库放到开发板里面怎么都不行,经过师傅的讲解以后(下面的步骤很关键哦),在external下面新建一个文件夹,然后把.c文件和.h文件放进来,最重要的是要写一个Android.mk文件.然后编译安卓源代码就可以了. LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_ARM_MODE :=arm LOCAL_MODULE := **libHelloWorld**//一定要加上lib LOCAL_SRC_FILES := HelloWorld.c LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) include $(BUILD_SHARED_LIBRARY) 然后编译该目录,会在system/bin下面生成libHelloWorld.so.这个才是最终生成的so文件,把它push进开发板的system/lib目录下面,然后在开发板中运行刚才写的java代码生成的.class就可以了. 感悟 这个任务其实挺简单的,多查资料,实在搞不明白的问问师傅,很快就可以解决.

    时间:2019-10-01 关键词: C语言 java

  • 嵌入式 Linux 与物联网软件开发 ——C 语言内核深度解析

    C 语言是嵌入式 Linux 领域的主要开发语言。 对于学习嵌入式、单片机、Linux 驱动开发等技术来说,C 语言是必须要过的一关。C 语言学习的特点是入门容易、深入理解难、精通更是难上加难。很多用 C 语言写了多年单片机程序的老工程师转入嵌入式 Linux 领域后,都会觉得很难,甚至惊叹“为什么同样是 C 语言代码,我完全看不懂?”更不用说初学者了,大多数人都会有一种“很难精进、很难掌握”的感觉。本书就是为了解决这个问题。 朱有鹏老师在由嵌入式软件开发人员转为职业培训讲师后,试图找到一种方式能够将研发实践中的技能和技巧传授给学生,而不仅仅是冰冷晦涩的语法和知识点。没错,我们认为 C 语言既是一门技艺,也是一种能力,就好像开车、踢足球、厨艺等一样,不只是要“知道怎么回事儿”,还要“玩儿得好”才行。 《C语言内核深度解析》的原型思想和内容,发源于朱有鹏老师早些年的研发和学习经历,发展于后来数年的线下培训授课经历,并最终成熟于视频课程《4.C 语言高级专题》(隶属于《朱有鹏老师嵌入式Linux 核心课程》系列视频课程的第 4 部分)。该套视频课程于 2015 年 10 月录制完成,并在不到的一年时间内,已被上千人观看学习,创下了全好评的好成绩。 本书正是基于这套视频课程的课件整理而来,参与各章节整理和编写的都是学习了朱有鹏老师视频课程的学生,最终由朱有鹏老师和张先凤老师检验并完善成书。这些参与编写的同学有的已经工作数年、有的则尚未走出大学校园。选择他们合作创作本书,就是为了告诉读者:做技术并不要求你天赋异禀,只需要你感兴趣、愿意去探索和练习,你也可以成功。 《C语言内核深度解析》的另一大特色是,专门针对嵌入式 Linux 开发方向而设计。这并不是一句空话,本书的很多内容,如位操作、container_of 宏、内核链表、变参等,都是嵌入式 Linux 开发中重要的技能,而在一般的 C 语言书中并无过多介绍。 最后,本书并不是一本零基础系统学习 C 语言的书,而是一本定位为技能提升型的专著。如果你已经学过或者正在使用 C 语言,但苦于无法精进,或者在学习嵌入式 Linux 软件开发中遇到困难,那么试试这本书吧,一定会为你带来收获。

    时间:2019-10-01 关键词: 嵌入式 C语言

  • Subdirectory 【Introduction to Linux supplements】

    【Linux番外篇】 环境搭建 语法分析 【补充】烧写flash问题 【补充】简单谈谈Makefile和交叉编译工具链 【补充】 嵌入式中的 (volatile unsigned int )理解 【补充】s3c2440启动过程详细分析 【补充】adr和ldr的区别 【补充】位置无关码和位置有关码 【补充】为什么初始化SDRAM中 adrl r2, mem_cfg_val而不是 ldr r2,=mem_cfg_val 【补充】arm堆栈-(堆栈起始地址的选择) 【补充】ARM MMU页表框架 【补充】 关于*(mmu_tlb_base + (virtuladdr >> 20))的理解 【补充】NAND FLASH大页和小页的区别 【补充】关于ARM的PC指针异常返回处理(PC+8,PC+4,PC-4,PC-8情况) 【补充】常用Makefile格式分析 【补充】uboot makefile 中的unconfig 【补充】uboot1.1.6分析之第一阶段 【补充】uboot1.1.6分析之第二阶段 小实验 驱动相关 问题精选

    时间:2019-09-11 关键词: C语言 交叉编译

  • C++内存分配原则方法

    C++内存分配原则方法

    学了这么久的C++了,对与这一块还是很模糊,自己也总结了不少,今天看了一个不错的总结,由于没有分享,就转载过来了。附上原文地址: 点击打开链接 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。   堆,就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。   自由存储区,就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。   全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。   常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)   明确区分堆与栈   在 BBS 上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。   首先,我们举一个例子: void f() { int* p=new int[5]; }   这条短短的一句话就包含了堆与栈,看到 new,我们首先就应该想到,我们分配了一块堆内存,那么指针 p 呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针 p。在程序会先确定在堆中分配内存的大小,然后调用 operator new 分配内存,然后返回这块内存的首地址,放入栈中,他在 VC6 下的汇编代码如下:   00401028 push 14h  0040102A call operator new (00401060)  0040102F add esp,4  00401032 mov dword ptr [ebp-8],eax  00401035 mov eax,dword ptr [ebp-8]  00401038 mov dword ptr [ebp-4],eax         这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是 delete p 么?噢,错了,应该是 delete []p,这是为了告诉编译器:我删除的是一个数组,VC6 就会根据相应的 Cookie 信息去进行释放内存的工作。   好了,我们回到我们的主题:堆和栈究竟有什么区别?   主要的区别由以下几点:   1、管理方式不同;   2、空间大小不同;   3、能否产生碎片不同;   4、生长方向不同;   5、分配方式不同;   6、分配效率不同;   管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。   空间大小:一般来讲在 32 位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:打开工程,依次操作菜单如下:Project->Setting->Link,在 Category 中选中 Output,然后在 Reserve 中设定堆栈的最大值和 commit。注意:reserve 最小值为 4Byte;commit 是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。   碎片问题:对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。   生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。   分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。   分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。   从这里我们可以看到,堆和栈相比,由于大量 new/delete 的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP 和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。   虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。   无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候 debug 可是相当困难的 :)   对了,还有一件事,如果有人把堆栈合起来说,那它的意思是栈,可不是堆,呵呵,清楚了?   static 用来控制变量的存储方式和可见性   函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。   static 的内部机制:   静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main()函数前的全局数据声明和定义处。   静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。   static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。   static 的优势:   可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。引用静态数据成员时,采用如下格式:   ::   如果静态数据成员的访问权限允许的话(即 public 的成员),可在程序中,按上述格式来引用静态数据成员。    Ps:   (1) 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。   (2) 不能将静态成员函数定义为虚函数。   (3) 由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针,函数地址类型是一个“nonmember 函数指针”。   (4) 由于静态成员函数没有 this 指针,所以就差不多等同于 nonmember 函数,结果就产生了一个意想不到的好处:成为一个 callback 函数,使得我们得以将 c++ 和 c-based x window 系统结合,同时也成功的应用于线程函数身上。   (5) static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。   (6) 静态数据成员在时前面加关键字 static。   (7) 静态数据成员是静态存储的,所以必须对它进行初始化。   (8) 静态成员初始化与一般数据成员初始化不同:   初始化在类体外进行,而前面不加 static,以免与一般静态变量或对象相混淆;   初始化时不加该成员的访问权限控制符 private、public;   初始化时使用作用域运算符来标明它所属类;   所以我们得出静态数据成员初始化的格式:   ::=    (9) 为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

    时间:2019-09-03 关键词: 内存 C语言

  • 裸机_GPIO实验_C语言

    引入:我们执行C语言程序时候,Main函数是被谁调用?执行完要返回给谁? 答:编译器编译代码 = 启动文件(标准库文件) + hello.c;由启动文件来调用main,最后main返回给启动文件。 标准库文件是编译器自动在代码前添加的,用来设置C程序的堆栈(无对战空间没法运行C程序)等,然后调用main函数。所以我们执行裸板的时候,需要自己写启动文件,来得以调用C代码。 思路回顾 启动文件 @****************************************************************************** @ File:crt0.S @ 功能:通过它转入C程序 @****************************************************************************** .text .global _start _start: ldr r0, =0x53000000 @ WATCHDOG寄存器地址 mov r1, #0x0 str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启 ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K bl main @ 调用C程序中的main函数 halt_loop: b halt_loop 启动文件初始化两部分: 软件初始化: 0·设置栈(sp->SRAM)-SRAM不用初始化 1·设置返回地址 2·调用main 3·清理工作 硬件初始化: 1·关看门狗 2·初始化时钟(我们程序简单,速度慢点没关系,这里没用到) 3·初始化SDRAM(我们程序比较小,SRAM中4KB够用了,这里没用到) C代码 #define GPFCON (*(volatile unsigned long *)0x56000050) #define GPFDAT (*(volatile unsigned long *)0x56000054) int main() { GPFCON = 0x00000100; // 设置GPF4为输出口, 位[8:7]=0b01 GPFDAT = 0x00000000; // GPF4输出0,LED1点亮 return 0; } 关于*(volatile unsigned long *)的解释请看点补充知识嵌入式中的 *(volatile unsigned int *)理解 Makefile led_on_c.bin : crt0.S led_on_c.c arm-linux-gcc -g -c -o crt0.o crt0.S arm-linux-gcc -g -c -o led_on_c.o led_on_c.c #-g:加入调试信息 -c只编译不连接 arm-linux-ld -Ttext 0x0000000 -g crt0.o led_on_c.o -o led_on_c_elf #-Ttext 0x0000000:指定代码段地址0 arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.bin # binary:二进制的 -S:不从源文件复制重定位信息和符号信息到目标文件中去 arm-linux-objdump -D -m arm led_on_c_elf > led_on_c.dis # -D:反汇编所有段 -m arm:指定反汇编文件使用arm架构 clean: rm -f led_on_c.dis led_on_c.bin led_on_c_elf *.o

    时间:2019-09-02 关键词: C语言

  • JZ2440MMU段映射代码实现

    JZ2440MMU段映射代码实现

    实现流程: JZ2440V3的SDRAM物理地址范围处于0x30000000~0x33FFFFFF,S3C2440的寄存器地址范围都处于0x48000000~0x5FFFFFFF。在前面,通过往GPBCON和GPBDAT这两个寄存器的物理地址0x56000010、0x56000014写入特定的数据来驱动4个LED 开启MMU,并将虚拟地址空间0xA0000000~0xA0100000映射到物理地址空间0x56000000~0x56100000上,这样就可以通过操作地址0xA0000010、0xA0000014来达到驱动这4个LED的同样效果 另外,将虚拟地址空间0xB0000000~0xB3FFFFFF映射到物理地址空间0x30000000~0x33FFFFFF上,并在连程序时将一部分代码的运行地址指定为0xB0004000,看看能否令程序跳转到0xB0004000(即0x30004000)处执行 本例程序只使用一级页表,以段的方式进行地址映射。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB的空间(每个描述符对应1MB的虚拟地址),每个描述符占用4字节,所以一级页表占16KB(4096*4KB)。在此使用SDRAM开始的16KB(0x4000)来存放一级页表,所以剩下的内存开始物理地址为0x30004000! 详细知识补充和代码分析 程序代码: 第一部分:(head.S&init.c)运行地址设为0,关闭看门狗,初始化SDRAM,复制第二部分代码到SDRAM中(存放在0x30004000开始处),设置页表,启动MMU,最后到SDRAM中(地址0xB0004000)去继续执行 第二部分:(leds.c)运行地址设为0xB0004000,用来驱动LED head.S @************************************************************************* @ File:head.S @ 功能:设置栈指针,禁止看门狗,初始化SDRAM,将第二部分代码复制到SDRAM, @ 设置页表,启动MMU,然后跳到SDRAM继续执行led程序 @************************************************************************* .text .global _start _start: ldr sp, =4096 @ 设置栈指针,(4KB)以下都是C函数,调用前需要设好栈 bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器以使用SDRAM bl copy_2th_to_sdram @ 将第二部分代码复制到SDRAM bl create_page_table @ 设置页表 @ 令heed.S、init.c程序所在内存的VA和PA一样 @ 为了代码在开启MMU后能够没有任何障碍的运行 bl mmu_init @ 启动MMU ldr sp, =0xB4000000 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址) ldr pc, =0xB0004000 @ 跳到SDRAM中继续执行第二部分代码 @ 等价于ldr pc,=main halt_loop: b halt_loop init.c /* * init.c: 进行一些初始化,在Steppingstone中运行 * 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址 */ /* WATCHDOG寄存器 */ #define WTCON (*(volatile unsigned long *)0x53000000) /* 存储控制器的寄存器起始地址 */ #define MEM_CTL_BASE 0x48000000 /* * 关闭WATCHDOG,否则CPU会不断重启 */ void disable_watch_dog(void) { WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可 } /* * 设置存储控制器以使用SDRAM */ void memsetup(void) { /* SDRAM 13个寄存器的值 */ unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON 0x00000700, //BANKCON0 0x00000700, //BANKCON1 0x00000700, //BANKCON2 0x00000700, //BANKCON3 0x00000700, //BANKCON4 0x00000700, //BANKCON5 0x00018005, //BANKCON6 0x00018005, //BANKCON7 0x008C07A3, //REFRESH 0x000000B1, //BANKSIZE 0x00000030, //MRSRB6 0x00000030, //MRSRB7 }; int i = 0; volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE; for(; i < 13; i++) p[i] = mem_cfg_val[i]; } /* * 将第二部分代码复制到SDRAM */ void copy_2th_to_sdram(void) { unsigned int *pdwSrc = (unsigned int *)2048; //led.o的加载地址在连接脚本中被指定2048 //所以第二部分代码就存储在Steppingstone中地址2048之后 unsigned int *pdwDest = (unsigned int *)0x30004000; while (pdwSrc < (unsigned int *)4096) { *pdwDest = *pdwSrc; pdwDest++; pdwSrc++; } } /* * 设置页表 */ void create_page_table(void) { /* * 用于段描述符的一些宏定义 * 段描述符bit[11:0]=0b110000011110 */ #define MMU_FULL_ACCESS (3 > 20)) = (physicaladdr & 0xFFF00000) | MMU_SECDESC; //*(mmu_tlb_base + (virtuladdr >> 20)) = //*(TTB[31-14]+MVA[31-20]+00) = *(描述符地址) /* * SDRAM的物理地址范围是0x30000000~0x33FFFFFF, * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上, * 总共64M,涉及64个段描述符 */ virtuladdr = 0xB0000000; physicaladdr = 0x30000000; while (virtuladdr < 0xB4000000) { *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | MMU_SECDESC_WB; virtuladdr += 0x100000; //0x100000 = 1MB,段页表以1MB为单位 physicaladdr += 0x100000; } } /* * 启动MMU */ void mmu_init(void) { unsigned long ttb = 0x30000000; __asm__( "mov r0, #0n" "mcr p15, 0, r0, c7, c7, 0n" /* 使无效ICaches和DCaches */ "mcr p15, 0, r0, c7, c10, 4n" /* drain write buffer on v4 */ "mcr p15, 0, r0, c8, c7, 0n" /* 使无效指令、数据TLB */ "mov r4, %0n" /* r4 = 页表基址 ,取第0个符号*/ "mcr p15, 0, r4, c2, c0, 0n" /* 设置页表基址寄存器 */ "mvn r0, #0n" "mcr p15, 0, r0, c3, c0, 0n" /* 域访问控制寄存器设为0xFFFFFFFF, * 不进行权限检查 */ /* * 对于控制寄存器,先读出其值,在这基础上修改感兴趣的位, * 然后再写入 */ "mrc p15, 0, r0, c1, c0, 0n" /* 读出控制寄存器的值 */ /* 控制寄存器的低16位含义为:.RVI ..RS B... .CAM * R : 表示换出Cache中的条目时使用的算法, * 0 = Random replacement;1 = Round robin replacement * V : 表示异常向量表所在的位置, * 0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000 * I : 0 = 关闭ICaches;1 = 开启ICaches * R、S : 用来与页表中的描述符一起确定内存的访问权限 * B : 0 = CPU为小字节序;1 = CPU为大字节序 * C : 0 = 关闭DCaches;1 = 开启DCaches * A : 0 = 数据访问时不进行地址对齐检查;1 = 数据访问时进行地址对齐检查 * M : 0 = 关闭MMU;1 = 开启MMU */ /* * 先清除不需要的位,往下若需要则重新设置它们 */ /* .RVI ..RS B... .CAM */ "bic r0, r0, #0x3000n" /* ..11 .... .... .... 清除V、I位 */ "bic r0, r0, #0x0300n" /* .... ..11 .... .... 清除R、S位 */ "bic r0, r0, #0x0087n" /* .... .... 1... .111 清除B/C/A/M */ /* * 设置需要的位 */ "orr r0, r0, #0x0002n" /* .... .... .... ..1. 开启对齐检查 */ "orr r0, r0, #0x0004n" /* .... .... .... .1.. 开启DCaches */ "orr r0, r0, #0x1000n" /* ...1 .... .... .... 开启ICaches */ "orr r0, r0, #0x0001n" /* .... .... .... ...1 使能MMU */ "mcr p15, 0, r0, c1, c0, 0n" /* 将修改的值写入控制寄存器 */ : /* 无输出 */ //输出 : "r" (ttb) ); //输入 r=ttb,第0个符号 } leds.c /* * leds.c: 循环点亮4个LED * 属于第二部分程序,此时MMU已开启,使用虚拟地址 */ #define GPFCON (*(volatile unsigned long *)0xA0000050) // 物理地址0x56000050 #define GPFDAT (*(volatile unsigned long *)0xA0000054) // 物理地址0x56000054 #define GPF4_out (1

    时间:2019-09-02 关键词: 数据 C语言

  • 记找工作中的磕磕碰碰(持续更新)

    记找工作中的磕磕碰碰(持续更新)

    个人认为有趣的题的总结,希望对大家有帮助。 转载请标明出处:http://blog.csdn.net/callon_h/article/details/52430312 1. 蛇形矩阵 在腾讯的2016.9.1模拟考中最后的编程题 题目大意:给一个边长n,给出1-n^2的数字蛇形数据,如下  n=3  ⎡⎣⎢⎢187296345⎤⎦⎥⎥ output: 1 2 3  8 9 4  7 6 5 此题后来发现在另一篇博文中 http://blog.csdn.net/z_x_b5/article/details/51056536?locationNum=1 也有记录,思想接近,不过用的方法不尽相同吧,大家参考参考即可: #include int Ori_width,New_width; void resort(int *temp, int *temp1){ int i,j; if(New_width == 1 || New_width == 0){ if(New_width == 0) return; *temp1 = Ori_width*Ori_width; return; } else { //right for(i=0;i

    时间:2019-09-02 关键词: 腾讯 C语言

  • 初学者对51单片机结构C的常见误区和注意事项

    初学者对51单片机结构C的常见误区和注意事项

    1)C忌讳绝对定位。常看见初学者要求使用_at_,这是一种谬误,把C当作ASM看待了。在C中变量的定位是编译器的事情,初学者只要定义变量和变量的作用域,编译器就把一个固定地址给这个变量。怎么取得这个变量的地址?要用指针。比如unsigned chardata x;后,x的地址就是&x,你只要查看这个参数,就可以在程序中知道具体的地址了。所以俺一看见要使用绝对定位的人,第一印象就是:这大概是个初学者。2)设置SP的问题。原因和1差不对,编译器在把所有变量和缓冲区赋予地址后,自动把最后一个字节开始的地方,作为SP的开始位置,所以初学者是不必要去理会的。这体现C的优越性,很多事情C编译时候做了。3)用C的主程序结构:#include <reg52.h>void main(void){while(1);}这是个最小的成功的C程序,包括头部文件和程序主体。头部文件的名词解释:引用的外部资源文件,这个文件包括了硬件信息和外部模块提供的可使用的函数和变量的说明。可以用文本方式打开reg52.h,仔细研究下,会有一些写程序的体会。4)这样构成一个C项目在C中,常用项目来管理。项目一般分为两大块:C文件块和头部文件块。我们常把不同功能写在不同的C文件中,依靠项目的管理,最后把所有文件连接起来,这样就可以得到可以烧录的HEX文件或BIN文件。这些C文件中,有且只有唯一一个包括main()函数,和3)中一样的C文件。用头部文件把各个不同的C互相连接起来。一个C文件基本上要对应有一个H头部文件,这个H文件就包含本C文件中可以提供给外面使用的变量和函数,没有在H文件中列出的文件,可以算是该C文件的内部函数和变量,外部C不能使用。例子:a.C:unsigned char i;unsigned char mWork;void Test1(void){mWork++;}void Test2(void){i++;}a.h文件中:extern unsigned char i; extern void Test1(void);这样主程序M.c中:#include <reg52.h> /*C编译器内部自带的H文件,使用<>*/#include "a.h" /*自定义的H文件,一般用""*/void main(void){Test1(); /*使用a.c模块文件中的函数*/while(1){i++; /*使用a.c模块文件中的变量*/}}5)51家族核心都是基于8031的,有很多在此核心上进行扩展,有的把程序存储器放在内部:89c(S)51..,有的增加了RAM:89c(S)52..,有的增加了一些专用硬件80C552...,有的改变时钟时序W77E58...。市面上现在常用的主要有ATMEL公司的AT89X系列,PHILIPS的P87(89)x,台湾WINBOND的w77(78)x系列,Cygnal的C8051Fx系列。6)51单片机结构的C描述这里不讲51的具体结构,只是引导初学者快速理解51单片机的物理结构。寄存器和IO及其它硬件设备的地址名称,在相应的C头部文件中可以找到。51为reg51.h,52为reg52.h,以次类推,比如winbond的78E58就为w78e58.h这些H文件中的描述:srf,定义一个8位的设备。srf16,定义一个16位的设备。sbit,定义一个位的设备。用这些语句定义后,就可以在C中象汇编一样使用这些硬件设备,这是单片机应用比标准C特殊的地方,其它差别很少。7)在51系列中data,idata,xdata,pdata的区别data:固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。idata:固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中idata做指针式的访问效果很好)xdata:外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。pdata:外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG,建议少用。但也有他的优点,具体用法属于中级问题,这里不提。8)startup.a51的作用和汇编一样,在C中定义的那些变量和数组的初始化就在startup.a51中进行,如果你在定义全局变量时带有数值,如unsigned char data xxx=100;,那startup.a51中就会有相关的赋值。如果没有=100,startup.a51就会把他清0。(startup.a51==变量的初始化)。这些初始化完毕后,还会设置SP指针。对非变量区域,如堆栈区,将不会有赋值或清零动作。有人喜欢改startup.a51,为了满足自己一些想当然的爱好,这是不必要的,有可能错误的。比如掉电保护的时候想保存一些变量,但改startup.a51来实现是很笨的方法,实际只要利用非变量区域的特性,定义一个指针变量指向堆栈低部:0xff处就可实现。为什么还要去改?可以这么说:任何时候都可以不需要改startup.a51,如果你明白它的特性。

    时间:2019-08-07 关键词: 51单片机 C语言

首页  上一页  1 2 3 4 5 6 7 8 9 10 下一页 尾页
发布文章

技术子站

更多

项目外包