有关为微控制器编写高效的C代码的资源?[关闭]


15

这里需要认真的帮助。我喜欢编程。最近,我一直在在线阅读有关C语言的大量书籍(例如K&R)和文章/论坛。甚至尝试研究Linux代码(尽管我不知道该从哪里开始,但是窥探小型库有帮助吗?)。

我最初是一名Java程序员,在Java中相当干cut。如果程序太大,则将其切成类,然后进一步细分为函数。准则,例如,保持代码可读性并添加注释。使用信息隐藏和OOP技术。其中一些仍然适用于C。

我现在一直在用C进行编码,到目前为止,我可以使程序以某种方式工作。许多人谈论性能/效率,算法/设计,优化和可维护性。有些人比其他人压力更大,但是对于非专业的软件工程师,您经常会听到类似这样的信息:Linux内核开发人员不会仅仅接受任何代码。

我的问题是:我计划在不浪费任何资源的情况下为8位微控制器编写代码。知道我来自Java背景,所以事情已经不一样了...资源/书籍/链接/技巧将不胜感激。性能和大小现在很重要。针对8位微控制器的有效(最佳实践)C代码的资源/技巧?

此外,它还inline assembly扮演着至关重要的角色,并且坚持遵守微控制器标准。但是,效率是否有适用于所有人的一般经验法则?

例如: 随时register unsigned int variable_name;首选char。或使用 uint8_t ,如果你不需要大的数字。

编辑:非常感谢您的所有答案和建议。感谢大家为共享知识所做的努力。


2
在x86处理器上通常不是这种情况,但是在微控制器上,如果要确保获得最后的性能下降,则可能要改用汇编。
宫阪丽

3
@Rei:手工汇编很少(如果有的话)使用更少的内存,并且比现代C编译器更快。这是浪费时间代码在装配时,它可以(如应该)在C.做
mattnz

1
@mattnz现代,您在谈论什么新东西?老实说,近十年来我都没有为微控制器编写代码。
宫阪丽

2
一个简单的提示:在微控制器中,“如果更简单,那就更快”仍然是正确的。在复杂的芯片(ARM和更高版本)上,硬件进行了很多优化,直到您进行测试,才知道。
哈维尔

1
@ReiMiyasaka大大增加了维护成本。一个好的C编译器可以产生与经验丰富的程序员几乎相同的代码。

Answers:


33

我有20多年的嵌入式系统,主要是8和16微米。对您问题的简短回答与其他任何软件开发都一样-不要优化,直到您知道自己需要,然后再优化,直到您知道自己需要优化的东西。编写代码,使其首先可靠,易读且可维护。在嵌入式系统中,过早的优化同样是一个问题,如果不是更多的话

当您在“不浪费任何资源”的情况下进行编程时,您是否认为您的时间是一种资源?如果没有,那是谁在为您付出时间;如果没有人,您是否有更好的办法来做。一旦选择,任何嵌入式系统设计人员必须要做的是硬件成本与工程时间成本。如果您要出货100个单位,使用更大的微型产品,则为100,000个单位,每单位节省1.00美元与1人年的软件开发相同(忽略上市时间,机会成本等),如果您以100万个单位开始,因为对资源的使用着迷而获得投资回报,但要小心,因为许多嵌入式项目从未设计出100万个大关,因为他们打算出售100万个(初期投资高,生产成本低),然后倒闭。

就是说,(小型)嵌入式系统需要考虑和注意的事情,因为它们会以意想不到的方式使它停止工作,而不仅仅是使其运行缓慢。

a)堆栈-您通常只有很小的堆栈大小,并且堆栈框架的大小通常很有限。您必须时刻了解堆栈利用率。请注意,堆栈问题会导致一些最隐蔽的缺陷。

b)堆-同样,堆大小较小,因此请注意不要分配的内存。碎片成为一个问题。有了这两个,您需要知道用尽时要做什么-由于OS提供分页,因此在大型系统上不会发生这种情况。即,当malloc返回NULL时,您是否检查它以及做什么。每个锦葵都需要检查和处理程序,代码膨胀吗?作为指导-如果有其他选择,请不要使用它。由于这些原因,大多数小型系统不使用动态内存。

c)硬件中断-您需要知道如何安全,及时地处理这些中断。您还需要知道如何制作安全的重入代码。例如,C标准库通常不能重入,因此不应在中断处理程序中使用。

d)组装-几乎总是过早的优化。为了达到C不能做的事情,最多只需要少量(内联)。作为练习,编写一个手工组装的小方法(从头开始)。在C中执行相同的操作。测量性能。我敢打赌C会更快,我知道它将更加可读,可维护和可扩展。现在,在练习的第二部分-用汇编和C语言编写一个有用的程序。
作为另一个练习,请看一下Linux内核是多少汇编程序,请阅读下面有关linux内核的段落。

值得知道如何去做,甚至可能精通一两个常见的微型语言。

e)早在70年代初(40年前),“ register unsigned int variable_name”,“ register”一直是向编译器的提示,而不是指令,这一直很有意义。在2012年,这是浪费键击的,因为编译器非常智能,而micros指令集却如此复杂。

回到您的linux注释-这里的问题是,我们谈论的不是仅仅100万个单位,我们谈论的是数以亿计的生命,它的寿命是永远的。为了使它尽可能地人性化而付出的工程时间和成本是值得的。尽管这是最佳工程实践的一个很好的例子,但对于大多数嵌入式系统开发人员来说,像linux内核所要求的那样脚是商业自杀。


4
mattnz:这是.stackexchange网站上最可爱的答案之一。
艾哈迈德·马苏德

1
我无法改善这个答案。我可能只补充一点,将汇编代码很少,使性能感,但它可能是有意义的东西像戳I / O芯片或其他硬件技巧,可能不容易C.做
麦克Dunlavey

@mattnz感谢您的正确回答。+1
AceofSpades 2012年

1
有时需要@MikeDunlavey汇编程序以获取准确的时间。刚刚完成了一个视频叠加,该视频叠加使用位敲击在I / O引脚上生成NTSC视频,时序以下列方式表示:-高电压持续3个时钟周期,然后低电压持续6个周期,然后....
马丁•贝克特

@马丁:完全有道理。自从我在该级别进行编码以来已经有很长时间了。
Mike Dunlavey 2012年

3

您的问题(“不浪费资源”)过于笼统,因此很难提供太多建议。从字面上看,如果您不想浪费资源,也许您应该退后一步,评估是否需要做任何事情,即是否可以通过其他方式解决问题。

另外,有用的建议在很大程度上取决于您的约束-您要构建哪种系统,以及要使用哪种CPU?它是一个硬实时系统吗?您有多少内存用于存储代码和数据?它本身支持所有C操作(最著名的是乘法和除法)以及什么类型的支持?更一般而言:阅读并理解整个数据表。

最重要的建议:保持简单。

例如:忘记复杂的数据结构(哈希,树,甚至可能是链表),并使用固定大小的数组。只有在通过测量证明阵列太慢之后,才可以使用更复杂的数据结构。

另外,不要过度设计(Java / C#开发人员倾向于这样做):编写简单的过程代码,而不必过多分层。抽象有代价!

熟悉使用全局变量和goto的想法[对于在没有异常的情况下进行清理非常有用];)

如果您必须处理中断,请阅读有关重入的信息。编写可重入代码非常简单。


3

我同意马腾兹的回答 -大多数情况下。30年前,我开始在8085上进行编程,然后在Z80上进行编程,然后迅速移植到8031。之后,我去了68300系列微控制器,然后是80x86,XScale,MXL和(后来的)8位PICS。猜测意味着我已经转了整整一个圈。作为记录,我可以说,尽管有针对性的代码重用,但是面向对象的FAE仍在使用几个主要的微处理器制造商。

我在批准的答案中没有看到关于目标处理器类型和/或建议的体系结构的讨论。它是0.50 $ 8苦味有限的记忆吗?它是具有流水线和8Meg闪存的ARM9内核吗?内存管理协处理器?它会有操作系统吗?一会儿(1)循环吗?最初生产10万台的消费类设备?一个有雄心壮志和梦想的创业公司?

虽然我确实同意现代​​编译器在优化方面做得很好,但是30年来我从未从事过一个项目,在这个项目中,我没有停止调试器并查看生成的汇编代码,以了解幕后到底发生了什么(当然,当流水线和优化开始起作用时,这是一场噩梦),因此组装知识很重要。

而且我从未有过CEO,工程副总裁,客户,他们没有强迫我尝试将一加仑的油塞入一个夸脱的容器中,或者没有通过使用软件解决方案来解决硬件问题而节省了0.05美元(嘿,这仅仅是软件对吗,有什么困难吗?)。内存(代码或数据)优化将始终有效。

我的观点是,如果您从纯粹的编程角度来看项目,那么您将获得较窄范围的解决方案。Mattnz的做法是正确的-使其正常运行,然后使其运行更快,更小,更好,但是在考虑编码之前,您仍然需要花费大量时间在需求和可交付成果上。


嗨,Gio,请避免在您的帖子中使用不必要的HTML,而应使用Markdown语法。因为<br />您可以按Enter键,而段落之间只需留空行即可。另外,当您引用其他答案时,请添加一个链接。在这一点上,可能只有几个答案,但可能会有更多答案,分布在许多页面上,并且您不清楚要回答哪个答案。查看修订历史记录以查看我的编辑。
yannis'1

@Gio感谢您提及其他重要因素。+1 :)
AceofSpades 2012年

+1-我的答案很好扩展。
mattnz 2012年

1

Manttz的回答很好地说明了如何进行“接近硬件”编程。毕竟这是C的意思。

但是,我想补充一点,尽管C中不存在“类”的严格关键字- 即使您接近硬件,也可以直接考虑使用C中的面向对象编程。

您可以考虑以下答案:C程序的OO最佳实践,这解释了这一点。

这里有一些资源,可以帮助您用C编写好的面向对象的代码。

一种。C
b中的面向对象编程。这是人们交流想法的好地方
。这是整本书

我想向您推荐的另一个好资源是:

编写伟大的密码系列。这是两册书。第一本书涵盖了较低级别机器的非常重要的方面。第二本书涉及的是“低级思考-高水平写作”


1

你有几个问题。首先,您是否希望此项目/代码可移植?可移植性使您付出了性能和尺寸的代价,您选择的平台和要执行的任务是否可以承受多余的尺寸和较低的性能?

是的,绝对在8位计算机上返回无符号字符而不是无符号int或shorts是提高性能和大小的一种方法。同样,在16位计算机上使用无符号短裤,在32位计算机上使用无符号整数。您可以很容易地看到,例如,如果您只是在各处使用无符号整数来接管整个系统的可移植性(例如,ARM将其推向最低功耗,最小的设备市场),那么代码将是巨大的浪费。一个8位的微型 您当然可以只使用unsigned而不使用int或short或char并让编译器选择最佳大小。

不仅是内联汇编,而且是汇编语言。内联汇编是非常不可移植的,比仅调用asm函数更难编写。是的,您可以烧掉呼叫建立和返回,但要以更轻松的开发,更好的维护和控制为代价。该规则仍然适用,只有在确实需要时才将其编写为asm,您是否已完成工作以得出编译器输出是该领域的问题,以及通过手工完成可以获得多少性能提升的结论。然后回到可移植性和维护,一旦您开始混合使用C和asm,并且每次您混合使用C和asm可能会损害您的可移植性,并可能使项目的可维护性降低,这取决于其他人在做什么或是否在做此工作。是您现在正在开发的产品,其他人则必须继续保持下去。完成该分析后,您将自动知道是否必须进行内联或直接装配。我在该领域已有25年以上的经验,每天都编写C和asm混合物,生活在硬件/软件层上/上,并且,永远不要使用嵌入式asm。很少花费精力,也不需要编译器特定的代码,我会尽可能(几乎到处)编写编译器非特定代码。

整个问题的关键是反汇编C代码。了解编译器对代码的处理方式,以及随时间的推移(如果需要),您可以学会操纵编译器以生成所需的代码,而无需过多地使用asm。随着更多的时间,您可以学习操纵编译器以跨多个目标生成有效的代码,从而使代码更可移植,而不必诉诸于asm。您应该毫无疑问地看到为什么无符号字符作为函数的状态返回比在8位微控制器上的无符号字符更好地工作,同样,无符号字符在16位和32位系统上也会变得更加昂贵(某些体系结构可以帮助您)出来,一些不)。

某些8位微控制器非常不适合编译器,并且没有编译器会产生良好的代码。没有足够的需求为这些设备创建编译器市场,从而为那些目标创建出色的编译器,因此那里的编译器吸引了更多的业务,非asm程序员,而不是因为编译器比手写asm更好。进入这个世界的“臂膀”和“小兵”正在改变该模型,因为您的目标确实具有在其上进行了大量工作的编译器,产生相当好的代码的编译器等。对于具有此类处理器的微型计算机,您当然仍然拥有在这种情况下,您不得不使用asm,但这种情况并不常见,只告诉编译器您要执行的操作比不使用它要容易得多。请注意,操作编译器不是丑陋的,不可读的代码,实际上,它是相反的,干净的,直接的代码,也许重新安排了一些项目。控制函数的大小和参数的数量,这种事情会在编译器输出中产生巨大的不同。避免使用编译器或语言KISS的功能,使其保持简单愚蠢,经常生成更好,更快的代码。


您没有说明所生产产品的类型,我认为这是非常高的数量,低利润率或具有疯狂利润率的特定利基市场。这对于决定业务水平至关重要,如果您应该使用小型的8位微型和手工组装的汇编器,还是使用较大的微型和C。为了回应您的意见(已删除?),我确实使用8位微型,但是我们从大于所需的微型开始,并且仅在BOM成本成为问题时才进行修订。)上市时间,操作成本和摊销的开发成本使我们可以将BOM增加10或20美分。
mattnz 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.