C ++是否适合嵌入式系统?


167

一个普遍的问题,在这里和其他地方。C ++是否适合嵌入式系统?

微控制器?实时操作系统?烤面包机?嵌入式PC?

OOP在微控制器上有用吗?

C ++是否会将程序员从硬件中移走太远以至于效率不高?

Arduino的C ++(没有动态内存管理,模板,异常)是否应被视为“真正的C ++”?

(希望这个Wiki将成为包含这场潜在的圣战的地方)


5
快速提问:当您说嵌入式时,您是指微控制器吗?微处理器?嵌入式x86 /嵌入式PC?
J. Polfer 2010年

1
我无意发动一场圣战;目的是要了解您的反对意见。
J. Polfer 2010年

2
之前已经有几个问题提出来了,所以我认为一个中心位置会很好。
Toby Jaffey

4
C ++与嵌入式是一个有争议的话题。我有很强的见解,但我认为提出问题并在得分上发挥作用是不公平的。我希望社区Wiki能够使讨论更加平衡。
Toby Jaffey

13
这是一个不好的问题,因为在确定特定语言及其相关行李是否合适时,“嵌入式”是毫无意义的属性。重点是小型系统与大型系统,小型系统未在其中运行操作系统,内存有限,可能不是von-Neuman,在调用堆栈,数据堆栈上可能有各种硬件限制,您不能只是动态分配Mb甚至kb等。大多数微控制器都是“小型”系统。单板计算机通常是嵌入式的,但通常是“大型”系统。
奥林·拉斯洛普

Answers:


135

是的,C ++在嵌入式系统中仍然有用。就像其他人所说的那样,它仍然取决于系统本身,就像8位uC在我的书中可能是不行的,即使那里有编译器并且有人这样做(颤抖)。即使在8位微型环境中,即使将C ++缩减为“ C +”之类的东西,使用C ++仍然有一个优势。我所说的“ C +”是什么意思?我的意思是不要使用new / delete,避免异常,避免具有继承的虚拟类,可能一起避免继承,使用模板时要非常小心,使用内联函数而不是宏以及使用const变量来代替#defines

我从事嵌入式系统的C和C ++工作已有十多年了,由于一些现实世界的问题动摇了我的天真,我对C ++的年轻热情肯定已经减弱。我已经看到了嵌入式系统中最糟糕的C ++,我将其称为“ CS程序员在EE世界中疯狂”。实际上,这就是我正在与客户合作以改善他们拥有的这一代码库的东西。

C ++的危险在于,它是一个非常强大的工具,就像一把两刃剑,如果不正确地接受其语言和通用编程本身的教育和训练,就会割伤您的胳膊和腿。C更像是单刃剑,但仍然一样锋利。使用C ++,很容易获得非常高级的抽象并创建混淆的接口,这些接口从长远来看变得毫无意义,这在一定程度上是由于C ++能够灵活地解决具有许多不同语言功能(模板,OOP,程序, RTTI,OOP +模板,重载,内联)。

我完成了由C ++专家Scott Meyers进行的两个4小时的有关C ++嵌入式软件的研讨会。他指出了一些我从未考虑过的关于模板的东西,它们还可以帮助创建安全关键代码。关键是,您不能在必须满足严格的安全关键代码要求的软件中包含无效代码。模板可以帮助您完成此任务,因为编译器仅在实例化模板时创建所需的代码。但是,必须对使用它们进行更彻底的教育,以便正确地为此功能设计,这在C语言中很难完成,因为链接器并不总是优化死代码。

Scott Meyers在模板和明智地使用内联方面非常支持,我必须说,我仍然对模板感兴趣。我倾向于避开它们,即使他说只应将它们应用到成为最佳工具的地方。他还指出,C ++为您提供了制作非常好的接口的工具,这些接口易于正确使用,而难以错误使用。同样,这是困难的部分。在您知道如何以最有效的方式应用这些功能成为最佳设计解决方案之前,必须先熟练掌握C ++。

OOP也是如此。在嵌入式世界中,您必须熟悉编译器将要吐出的哪种代码,才能知道您是否可以处理运行时多态性的运行时成本。您还需要愿意进行测量,以证明您的设计将满足您的最后期限要求。新的InterruptManager类会使我的中断等待时间太长吗?还有其他形式的多态可以更好地解决您的问题,例如C可以做到的链接时多态,但是C ++可以通过Pimpl设计模式(不透明指针)来实现

我要说的就是C ++在嵌入式世界中占有一席之地。您可以恨自己想要的一切,但它不会消失。可以用非常有效的方式编写它,但是要比使用C来学习正确的方法要困难得多。在解决问题和表达更好的接口方面,有时它可以比C更好地工作,但是同样,您必须教育自己,不要害怕学习方法。


1
这与我从其他嵌入式系统顾问那里读到的内容一致。我一直被教导说,使用C,您会不断地自我削减,但是C ++中的错误会变得越来越少,但是当您搞砸了时,就会失去一条路。感谢您用我没有的一些专业知识做出清晰的答复。
Kortuk 2010年

3
注意到计算机科学专业的学生在EE领域疯狂。在我的工作中,我们遇到的最糟糕的代码是由CS专业编写的。我们花了很长时间试图教他硬件。他使用UML制作了一个结构化的系统,并使用适当的继承等在Java中基于它构建了整个系统。它一直有效,直到发生任何更改为止,然后添加功能或进行彻底的重新设计是一项糟糕的补丁工作。该代码几乎无法使用,因为他对继承的整体混淆不清。
Kortuk 2010年

2
不仅是虫子会咬你,而且无法维护的代码也会咬你。当然,当您开始使用这些纯净的C ++功能进行项目开发时,一切都会顺利进行,但是如果在开发过程中不花大力气进行重构,那么2或3年后就会产生熵。这就是我现在面临的问题。.随着时间的流逝,代码在C ++中的运行速度更快。
杰·阿特金森

2
UML和状态机。您确实需要在state-machine.com上查看Miro Samek的资料。他建立了一个高效的系统,该系统易于重构和更改,但是确实需要花费一些时间。
杰·阿特金森

2
这实际上取决于您的系统以及可用的内存量。您是在RAM很少的8位微型计算机上编写代码吗?然后,最好避免避免在抽象接口上发疯。如果要编写带有内存块的32位嵌入式系统之类的东西,那就去吧。您真的必须权衡一下。例如,每次在该类上使用“虚拟”世界时,对于您声明该对象的每个实例,都会获得一个额外的指针,该指针可能是8位,16位或32位,具体取决于系统。您甚至都不会意识到,伙计,
杰伊·阿特金森

56

C ++绝对适用于嵌入式系统。现在,我将是否存在良好的开发工具(或缺乏)作为我是否使用特定微处理器的主要标准。

可以在嵌入式系统上很好使用的C ++区域,因为它们的资源成本低:

  • 善用类/结构带来的模块化
  • 模板,如果编译器在有效地编译它们方面做得很好。模板是将算法重用引入不同数据类型的好工具。

可以的区域:

  • 虚拟函数-我曾经反对这样做,但是资源成本非常小(每个一个vtable 而不是每个对象一个;每个对象一个指向vtable的指针;每个虚拟函数调用一个解引用操作),并且这样做的好处很大是它允许您拥有一个包含几种不同类型对象的数组,而不必知道它们是什么类型。最近,我使用了这个对象数组,每个对象代表一个I2C设备,每个对象都有单独的方法。

不使用的区域,主要是因为小型系统无法接受的运行时开销:

  • 动态内存分配-其他人已经提到了这一点,但是使用动态内存分配的另一个重要原因是它表示时序的不确定性;使用嵌入式系统的许多原因都是为了实时应用。
  • RTTI(运行时类型信息)-内存开销相当大
  • 例外- 由于执行速度不佳,一定不能

感谢您的输入。有趣,非常类似于我所读的内容。
Kortuk 2010年

1
实际上,动态内存分配是可以的,有时是不可避免的。问题是动态内存的重新分配(和后续重用)。RTTI是记忆猪,我同意这一点。但是异常有什么问题呢?
Wouter van Ooijen 2011年

1
@WoutervanOoijen:异常的问题是,如果在/ 块内foo调用,并创建了一些对象并进行了调用,从而引发了异常,则系统必须以某种方式为创建的对象调用析构函数,然后再将控制返回给。除非将异常完全禁用,否则将无法知道是否会抛出异常,因此必须包括额外的代码以允许这种可能性。我希望看到带有“检查的异常”的C ++变体来解决这个问题;如果例程可以允许异常逃逸……bartrycatchbarbozbarfoobarboz
超级猫

1
...必须这样声明,那么编译器只需要在此类例程的调用程序中包括异常处理代码即可。可以肯定的是,必须添加所有必需的声明,同时希望避免不必要的声明,这会有些负担,但是这将允许在有用的地方使用异常,而不会在没有使用的地方增加开销。
supercat 2012年

3
@WoutervanOoijen:顺便说一句,如果我正在设计用于在ARM上进行此类异常处理的ABI,我将指定调用可能通过异常退出的例程的代码应使R14指向所需返回地址之前两个字节的地址(这将如果调用方在CALL指令后加上一个16位字,则自然发生)。然后,被调用的例程将通过add r15,r14,#2而不是mov r15,r14; 正常退出;通过异常退出ldrhs r0,[r14] / add r15,r14,r0。正常退出的周期成本为零,并且没有堆栈帧限制。
supercat 2012年

36

是的,C ++当然适合嵌入式系统。首先,让我们澄清一些关于C和C ++之间区别的误解:

在嵌入式微型计算机中,如果您担心时间或空间限制,则总是需要谨慎使用高级语言。例如,许多MCU不能很好地处理指针,因此在使用堆栈时效率很低。这意味着您在使用数组和指针以及递归将变量传递给函数时要格外小心。简单的C行如下:

a[i] = b[j] * c[k];

可以生成大约4页的指令,具体取决于这些变量的性质。

每当您使用任何高级语言,并且担心时间和空间限制时,您都需要知道该语言的每个功能如何转换为MCU上的机器指令(至少,您使用的每个功能)。对于C,C ++,Ada,无论如何都是如此。可能所有语言都将包含在小型MCU上无法有效翻译的功能。始终检查反汇编清单,以确保编译器不会生成一些琐碎的指令。

C是否适合嵌入式MCU?是的,只要您注意生成的代码即可。
C ++是否适合嵌入式MCU?是的,只要您注意生成的代码即可。

这就是我认为即使在8位MCU上C ++也比C更好的原因:C ++为以下方面提供了改进的支持:

  • 资料隐藏
  • 更强的打字/检查
  • 使用类的多周边透明
  • 模板(如果谨慎使用,一如既往)
  • 初始化清单
  • const

这些功能都没有比C的典型功能重。

当您最多移动16或32位MCU时,使用C的较重功能(堆栈,堆,指针,数组,printf等)就变得有意义了,以同样的方式,在功能更强大的MCU上变得合适使用C ++的重功能(堆栈,堆,引用,STL,新建/删除)。

因此,无需对PIC16上的C ++想法感到震惊。如果您正确地了解您的语言和MCU,那么您将知道如何一起有效地使用它们。


3
这是对该问题的非常明确和合理的答案。+1欢呼!
vicatcu'2

1
a[i] = b[j] * c[k];根据这些变量的性质,可以生成大约4页的指令。” 如果您的MCU /编译器这样做,是因为您使用的是80年代的车库业余CPU。
隆丁

@Lundin-叹气。不,这意味着您使用的是便宜的小MCU,该MCU被设计为尽可能小巧且便宜,而不要像堆栈索引那样复杂。
Rocketmagnet 2015年

2
@Rocketmagnet好吧,也许是在1990年代?如今,8脚的8种苦酒的价格与32种苦酒的价格相同。选择前者的唯一原因是电流消耗。关于那些没有堆栈的极其繁琐的8位寄存器:如果您为如此有限的MCU编写C而不是汇编程序,则可能做错了。然后,生成的4页是您自己的错误,因为它无法为CPU编写过于复杂的程序,而从本质上来说,C是执行该任务的错误工具。(我过去在Freescale RS08上这样做,这是一个非常愚蠢的主意。)
Lundin

@Lundin 32位处理器没有必要快于16位。这很明显可以追溯到PC世界中从16到32位程序转换的时代。
Barleyman

24

我总是觉得阅读这些辩论很有趣。关于各种可用语言的优缺点的学术讨论并没有那么多,而是因为您通常可以根据某人的工作/经验/兴趣领域来确定某人对该主题的立场。就在那儿,带有“过早优化”的论点是CS专业和维护程序员左右引用Knuth,以及那些在现实世界中工作的人,他们认为性能太疯狂了(我是后者的成员)公平起见)。

最终,您可以使用C或C ++开发出色的软件,或在此处插入语言。它取决于开发人员的能力,而不是语言。通常,只有当您选择了错误的语言并且现在需要将其转换为解决问题时,才需要成为语言专家,在大多数情况下,这是唯一需要深入了解晦涩功能或编译器的情况实现目标的技巧。

我经常听到人们以“我是X语言专家等等”开始争论,说实话,我立即抹黑了这些人,因为在我看来,他们已经从错误的角度解决了问题,此后的一切都被污染了。他们渴望使用他们的工具来解决问题,并表明它有多“酷”。

我经常看到开发人员首先选择工具集,然后再尝试解决其问题,这是完全错误的,并导致了解决方案。

正如我在对另一个答案的评论中提到的那样,这些语言之争常常演变成争论:语言X允许程序员做更多愚蠢的事情。在娱乐阅读的同时,所有这些陈述实际上意味着您在雇用优秀的开发人员时遇到了问题,需要直接解决该问题,而不是通过继续雇用不良的开发人员并选择使他们做的事少的工具来试图解决问题。尽可能的损坏。

我认为优秀的开发人员,无论是软件开发还是硬件开发,都要研究问题,设计解决方案并找到使他们能够以“最佳方式”表达解决方案的工具。使用了3-4种语言/开发工具进行项目选择新工具后,所需的工具是否是您以前从未使用过的工具并不重要。

当然,“最佳方法”是一个主观术语,还需要在研究阶段进行定义。一个人需要根据眼前的问题考虑许多问题:性能,表达的难易程度,代码密度等。我之所以没有将可维护性列入该列表是有原因的,我不在乎您选择哪种语言,如果您选择了合适的工具并花时间来理解应该免费的问题。难以维护代码通常是由于选择了错误的工具或较差的系统结构而导致的,这导致难看的杂乱无章的混乱使其无法正常工作。

声称任何一种语言都比其他任何一种都“好”,而没有定义一个特定的问题。面向对象的方法并不总是比功能方法更好。有一些问题非常适合面向对象的设计范例。有很多没有。关于人们似乎喜欢使用的许多语言功能,可以做出相同的陈述。

如果您花费超过20%的时间在实际键入代码的问题上,则可能是您生产的系统很差,或者开发人员的能力很差(或者您还在学习)。您应该将大部分时间花在解决问题并确定应用程序各部分之间的交互方式上。将一群才华横溢的开发人员放在一个带有标记板和问题的房间中,并告诉他们除非他们对整个系统感到满意,否则他们不能编写任何代码或选择任何工具,这将大大提高系统的质量。输出和加快开发速度,而不是选择任何热门的新工具都可以保证缩短开发时间。(将scrum开发作为与我的论点相反的参考)

通常,不幸的现实是,许多企业只能通过编写的行数或看到“有形的输出”来衡量开发人员的价值。他们将在带有标记板的房间中的3周视为生产力的损失。开发人员经常被迫加速开发的“思考”阶段,或者被迫使用公司内部某个政治问题所设定的工具,“我老板的兄弟为IBM工作,所以我们只能使用他们的工具”,这种垃圾。或更糟糕的是,您会从公司那里收到一组不断变化的需求,因为它们无法进行适当的市场研究或不了解更改对开发周期的影响。

抱歉,这个话题有点偏离主题,我对此主题有很强的见解。


2
现在,我不会敲打某些嵌入式系统上应用程序级别(在驱动程序之上)的单元测试。在开发阶段的早期就进行单元测试并消除bug的即时反馈具有一定的价值,但是在整个TDD范式中诞生设计的整个过程在我看来有点不明智。在开始编写代码之前,我宁愿花一些时间“思考”问题并在脑海中,在纸上或在白板上画出问题。我还认为TDD鼓励市场不要对需求进行前期研究,因为它应该有助于不断地改变需求。
杰伊·阿特金森

2
最后要对我的超长评论发表最后的评论。我们不需要语言专家来进行设计。我们需要可以使用该语言的专家设计师。
杰伊·阿特金森

1
PRD =产品需求文件,MRD =市场需求文件,TRD =技术需求文件。TDD =测试驱动开发。
杰·阿特金森

1
@Mark-我同意您的前期设计观点,但只限于一点。我认为,如果a)您的要求相当稳定/已知,并且b)进行设计的开发人员经验丰富,那么就可以进行大量的前期设计工作。在上一个 这项工作使我承担了进行设计的任务,而团队负责人花了很多时间来思考这个问题,然后我想到:“要做的事真愚蠢!预先设计可以省钱(请参阅代码完整书)?” 但是在编码中,我发现了很多我不知道要寻找的东西。如果我做了很多设计并减少了代码时间,那将是浪费。JME。
J. Polfer 2010年

1
@sheepsimulator我显然同意第二点,我假设主导系统架构师是有经验的开发人员。关于第一点,我实际上不同意。我认为您对需求的更改期望越高,则在设计阶段应该花费的时间就越多,因为您需要进行良好的,易于更改的设计。我知道有些哲学提出快速发展。在某些情况下,这很适合工作人员中许多糟糕的或缺乏经验的程序员。所有这些设计哲学都归结为“我没有为设计灵活的系统而祈祷,所以不要浪费时间去尝试”。
马克

17

任何语言都适用于嵌入式系统。嵌入式仅表示:与免费计算机相反,是较大设备的一部分。

当要求使用(硬)实时资源有限的系统时,该问题具有更大的相关性。

对于实时系统,C ++是在对严格的时间限制进行​​编程时仍然适合的最高语言之一。除了堆使用(免费运算符)外,它没有不确定的执行时间的构造,因此您可以测试程序是否满足其计时要求,并且有更多的经验甚至可以预测它。当然,应该避免使用堆,尽管新的运算符仍然可以用于一次性分配。C ++通过C提供的结构可以在嵌入式系统中得到很好的利用:OO,异常,模板。

对于资源非常有限的系统(8位芯片,RAM不足几Kb,没有必需的堆栈),尽管可能仍被用作“更好的C”,但完整的C ++可能不合适。

我认为不幸的是,Ada似乎仅在某些壁ni中使用。从很多方面来说,它都是Pascal ++,但是没有与一开始就已经很混乱的语言向上兼容的负担。(编辑:严重的混乱当然是C。帕斯卡是一门美丽但有些不切实际的语言。)

================================================== ==============

编辑:我正在键入一个新问题的答案(“在什么情况下,当我们对微控制器进行编程时,C ++是必需的吗?”),因此我将添加我写的内容:

使用任何编程语言从来没有一个绝对的理由,但是在特定情况下,可能存在具有或多或少影响的参数。在很多地方都可以找到有关此问题的讨论,其职位范围从“从不使用C ++用于微控制器”到“始终使用C ++”。我更喜欢最后一个职位。我可以提出一些论点,但您必须自己决定它们在特定情况下(以及在哪个方向)承担的重量。

  • C ++编译器比C编译器更为罕见。对于某些目标(例如12位和14位核心PIC),根本没有C ++编译器。
  • (优秀)C ++程序员比(优秀)C程序员稀少,尤其是在那些也(有些)电子学知识的程序员中。
  • C ++比C具有更多不适用于小型系统的构造(例如,异常,RTTI,频繁使用堆)。
  • C ++具有比C更丰富的(标准)库集,但是前一点的结果是C ++库经常使用不适用于小型系统的功能,因此不适用于小型系统。
  • 与C相比,C ++具有更多的构造,可让您脚踏实地。
  • C ++比C语言具有更多的构造,可以防止您用脚砸自己的脚(是的,IMO,这和上一个都是正确的)。
  • C ++具有丰富的抽象机制集,因此它提供了更好的编程方式,尤其是对于库。
  • C ++语言功能(例如,构造函数/析构函数,转换函数)使查看代码以查看生成的机器更加困难,因此语言构造的空间和时间成本也随之增加。
  • 由于C ++语言结构以更抽象的方式执行“正确的事情”,因此不必知道将它们准确地转换为机器代码的方式。
  • C ++语言标准发展迅速,并且被大型编译器(gcc,clang,microsoft)迅速采用。C的发展相当缓慢,对某些较新功能(可变数组)的采用感到恐惧,甚至在以后的标准中也有所恢复。这一点特别有趣,因为不同的人使用它来支持相反的立场。
  • C ++无疑是比C更加强大的工具。您是否相信程序员(或您自己)使用这样的工具来制作精美的雕塑,或者您担心他们会伤害自己,还是宁愿选择一款不太美观但风险较低的产品呢? ?(我记得我的雕塑老师曾经告诉我,钝器在某些情况下比锋利的工具危险。)

我的博客上有一些关于在小型系统(即微控制器)上使用C ++的著作。


15

以我的经验,C ++通常不适用于小型嵌入式系统。我的意思是微控制器和无操作系统的设备。

许多C ++ OOP技术都依赖于动态内存分配。在小型系统中通常不存在这种情况。

STL和Boost确实展示了C ++的强大功能,两者的足迹都很大。

C ++鼓励程序员抽象化机器,而在受限的系统中必须将其包含在内。

去年,我将商业远程桌面产品移植到了手机上。它是用C ++编写的,并且可以在Windows,Linux和OSX上运行。但是,它严重依赖STL,动态内存和C ++异常。要使其在WinCE,Symbian和无操作系统环境中运行,最明智的选择是C重写。


我同意使用小型系统,但是我认为我们对小型系统有不同的定义。当您拥有1kB的ROM且编写良好的C代码占用了除1字节以外的所有ROM时,这是一个很小的系统。
Kortuk 2010年

6
我并不是在说C不能拥有更小的占用空间,但是您可以仍然使用C ++,并为刚才讨论的内容获得非常相似的结果。我认为问题在于大多数OOP程序员习惯于具有动态内存并使用效率非常低的构造的系统,从而导致低功耗系统的代码完全无用。
Kortuk 2010年

4
所以你的意思是你不想使用C ++,而是想在C和C ++之间使用某种东西(让我们称之为C +?)。在那种情况下,我同意,人们在C ++中使用很多废话只是因为其可用,而不是因为其最佳。几乎任何一种语言都能够生成良好,快速的代码,这取决于其使用方式。关于语言的大多数圣战不是语言能力的结果,而是关于白痴做愚蠢事情有多容易的争论,这实际上是一个愚蠢的争论:p
Mark

2
“大多数针对语言的圣战不是语言能力的结果,而是关于白痴做白痴事情有多容易的争论,这实际上是一种白痴争论。” 这是一个非常愉快的句子。我需要你的姓氏,所以我可以引用那个名字。
Kortuk 2010年

1
我确实也没有在C中使用动态内存。我没有地方必须拥有它。从长远来看,我读到它会变得非常细分,并开始引起问题。我需要设计非常清楚的案例来解决内存不足的问题,并且需要能够准确监视剩余的内存量。
Kortuk 2010年

11

我希望在有关裸机和资源受限系统上的C ++的讨论中增加更多的热量。

C ++中的问题:

  • 异常尤其是一个RAM问题,因为所需的“紧急缓冲区”(例如,内存不足异常就在其中)可能大于可用的RAM,并且肯定对微控制器造成浪费。有关更多信息,请参见n4049n4234。应该将它们关闭(这是当前未指定的行为,因此请确保不要抛出)。第14研究组目前正在研究更好的方法。

  • RTTI可能永远都不值得开销,应该将其关闭

  • 大型调试版本,尽管在经典台式机开发中这不是问题,但如果调试不适合芯片,则可能是一个问题。问题来自于为清晰起见而添加的模板代码或额外的函数调用。这些额外的函数调用将被优化器再次删除,并且增加的清晰度或灵活性可能是一个很大的优势,但是在调试版本中,这可能是个问题。

  • 堆分配。尽管STL允许使用自定义分配器,但对于大多数程序员而言,这可能很复杂。堆分配是不确定的(即,不是实时的),即使进行了测试,碎片也会导致意外的内存不足情况发生。为了跟踪自由空间和变化的大小,堆所需的簿记可能是小对象的问题。通常最好使用池分配(在C和C ++中都是这样),但是对于只使用堆的C ++程序员来说,这可能是异常的。

  • 运行时多态和其他间接调用通常会对性能造成很大的影响,问题通常出在更多地方,因为优化器无法比实际获取和跳转到该地址看到更多内容。因此,在C和C ++中应避免进行间接调用,因为在C和C ++中,它们在文化中更加根深蒂固(并且在其他领域中非常有用)。

  • 与clib的隐式接口可能会出现问题。clib问题在C ++类别中可能是违反直觉的,但问题是由并发环境中的资源隐式共享引起的(在C中共享更明确)。通用newLib实现的使用通常会导致很多膨胀,这在uC中通常是不需要的,另一方面,newLibNanno并不是可重入的,因此必须序列化对其的访问(此处过于简化)。对于C来说也是一个问题,但是访问更明确。根据经验,除非您确定它不会以某种方式访问​​clib中的状态(例如,errorno或堆),否则基本上不应在ISR上下文中使用命名空间std中的任何内容。如果您使用线程(我更喜欢RTC)来覆盖new和delete以同步对malloc和free的访问,这也很重要。

总之,C ++存在一些问题,但它们基本上都是可修复的或可避免的。

现在对于C,这里的问题是高阶。我没有C语言的语法能力来抽象事物,而我无法在编译时执行优化或检查不变式。因此,我无法以某种方式正确地封装事物,即用户不需要知道它们的工作方式就可以使用它们,而我的大部分错误检测都是在运行时完成的(这不仅太迟,而且还会增加成本)。本质上,在C语言中通用的唯一方法是通过数据,我将格式字符串传递给printf或scanf,例如在运行时对其进行评估。然后,对于编译器而言,很难证明我没有使用某些选项,这些选项在传递正确的数据时理论上是可行的,这意味着潜在的死代码生成和优化潜力的损失。

我知道我可能会在这里发疯,但是我在32位微控制器上的经验是,在专家之间(如在C ++中可能是高度模板化的)编写的C和C ++的比较中,C ++是一种效率更高的语言。任何东西都必须是通用的(就像在任何库中一样),并且在非通用情况下它们基本上是等效的。对于新手来说,利用C ++中的专家库实现者的专业知识也更加容易。

同时,实际上只有很少的函数无法传递不正确的数据,只要输入不是int而是something我恰巧使用int作为表示方法的函数,那么就有可能获得它错误(传递无效值或“ otherThing”而不是“ something”)。在C语言中,检查用户是否输入错误的唯一方法是在运行时。在C ++中,我可以执行一些检查,而不是全部检查,但可以在编译时执行一些免费的检查。

归根结底,C团队通常与其最弱的程序员一样强大,并且由此产生的代码好处是多人游戏1或性能损失。我的意思是,在一个独特的设计决策独特的环境中,它要么只执行一项工作,要么仅执行一项唯一的工作,要么具有足够的通用性,就可以在多种环境中使用(其他微控制器,其他内存管理策略,其他延迟与性能)。吞吐量的取舍等),但具有固有的性能成本。

在C ++中,事物可以由专家进行封装,并在许多环境中使用,在这些环境中,编译时代码生成可适应特定任务,而静态检查可防止用户以零成本进行愚蠢的工作。在这里,我们在通用和快速之间的权衡要少得多,因此最终从成本与收益的角度来看,是一种性能更高,更安全和更具生产力的语言。

很好的批评是,仍然缺少用于嵌入式的良好C ++库,这可能会导致务实的决定,即在C ++编译器上主要使用C。在项目中仅使用C的决定本质上是出于意识形态的驱动,出于对遗留支持的需求,或者承认团队的纪律性不足以避免选择C ++而不是C可以完成的一系列愚蠢的选择。同时要有足够的纪律性,以至于不能做任何愚蠢的事情,而这在C语言中是无法防范的,而在C ++中是可以避免的。


我的回答还不错:)这个神秘的C ++爱好者会是谁?他的个人资料指出:“显然,这些用户更喜欢保持神秘感。” (英语不好,BTW)但是AHA的位置是“德国波鸿” .....在会议上见!
Wouter van Ooijen

是的,更新了我的个人资料;)很高兴知道您来emBO ++会很不错
odinthenerd

10

我的背景: 刚从贝尔实验室老程序员的学校培训结束;已经工作了3年,其中2个是本科生研究项目;VB.NET中的数据采集/过程控制。用了1.5年的时间在VB6中的企业数据库应用程序上工作。目前正在为具有2GB存储,512MB RAM,500MHz x86 CPU的嵌入式 PC 进行项目;使用C ++同时运行的几个应用程序,中间之间具有IPC机制。是的,我还年轻。

我的看法:在上述环境下, 我认为C ++可以有效地工作。诚然,硬实时性能不是我正在使用的应用程序的要求,在某些嵌入式应用程序中,这可能是个问题。但是,这是我学到的东西:

  • C ++从根本上不同于C(即,没有C / C ++)。尽管所有有效的C语言都是有效的C ++语言,但C ++语言却是一种截然不同的语言,需要学习如何使用C ++(而不是C)进行编程,以便在任何情况下都可以有效地使用它。在C ++中,您需要面向对象的程序,而不是过程式的程序,而不是两者的混合(大型类具有很多功能)。通常,您应该集中精力制作具有很少功能的小型类,并将所有小型类组合为一个更大的解决方案。我的一位同事向我解释说,我以前是在对象上进行过程编程的,这很麻烦,而且很难维护。当我开始应用更多的面向对象技术时,我发现代码的可维护性/可读性得到了提高。

  • C ++以面向对象开发的形式提供了其他功能,这些功能可以提供一种简化代码以使其更易于阅读/维护的方式。老实说,我认为在进行OOP时不会在性能/空间效率方面有所改进。但是我认为OOP是一种可以帮助将复杂问题分解为许多小片段的技术。这对从事代码工作的人员很有帮助,这一过程中的要素不容忽视。

  • 反对C ++的许多论点主要与动态内存分配有关。C也有同样的问题。您可以编写面向对象的应用程序而无需使用动态内存,尽管使用对象的好处之一是可以轻松地动态分配这些内容。就像在C语言中一样,您必须小心如何管理数据以减少内存泄漏,但是RAII技术在C ++中使这一过程变得更加简单(通过将动态内存封装在对象中来自动破坏动态内存)。在某些应用程序中,每个内存位置都很重要,这可能太繁琐而难以管理。

编辑:

  • WRT的“ Arduino C ++”问题:我认为没有动态内存管理的C ++ 仍然有用。您可以将代码组织到对象中,然后将这些对象放置在应用程序中的各个位置,设置回调接口等。既然我已经在C ++中进行开发,我可以看到在应用程序上分配了所有数据的多种方式。堆栈对于对象仍然有用。我会承认-我从未真正为Arduino编写过这样的嵌入式应用程序,因此我没有任何证据支持我的主张。我有机会在即将到来的项目中进行一些Arduino开发-希望我可以在那里测试我的主张。

2
我想谈谈您的第二点,您说这有助于将一个复杂的问题分解为许多小片段,并且应该忽略该功能。这就是我如此亲C ++的确切原因。对编程的大量研究表明,程序大小的线性增长使开发时间呈指数增长。这是相反的方法,如果您可以适当地拆分程序,则可以使开发时间呈指数衰减。这是迄今为止最重要的事情。
Kortuk 2010年

还有第二点:仅使用OOP设计方法就不会产生更多的分区代码。拥有良好的基础设计,开发人员如何表达该设计就可以了。OOP并未定义您正确地分离了代码,它提供了另一个选择,并且提供了这样做的外观,但是,它肯定不能实施良好的设计,这取决于开发人员。
马克

总是这样。我从来没有听说过可以实现良好设计的语言。我认为我们主要暗示这是开发人员的工作,而C ++使其易于以有组织的方式使用和实现。
Kortuk 2010年

@Mark-我同意。对我来说,这是一个学习过程。
J. Polfer 2010年

7

是的,C ++的问题在于代码占用量的增加。

在某些系统中,您需要计算字节数,在这种情况下,您将不得不接受接近系统界限的运行成本,这是C语言增加的开发成本。

但是,即使在C语言中,对于一个设计良好的系统,您也需要将所有内容封装起来。设计良好的系统很难,而C ++为程序员提供了一种结构化和受控的开发方法的场所。学习OOP是有成本的,如果您要切换到OOP,则您会接受它,并且在许多情况下,管理层宁愿继续使用C而不支付费用,因为很难衡量要转换的结果。提高生产力。您可以在这里看到嵌入式系统专家Jack Ganssle的文章。

动态内存管理是魔鬼。并非如此,魔鬼是自动路由,动态内存管理在PC上运行良好,但是您可以期望至少每隔几周重新启动PC。您会发现,随着嵌入式系统继续运行5年,动态内存管理可能会真正陷入困境并开始出现故障。Ganssle在他的文章中讨论了堆栈和堆之类的问题。

C ++中的某些事情更容易引起问题并占用大量资源,删除动态内存管理和模板是使C ++的足迹更接近C足迹的重要步骤。这仍然是C ++,您不需要动态内存管理或编写好的C ++模板。我没有意识到它们删除了异常,我认为异常是我在发行版中删除的代码的重要组成部分,但直到那时才使用。在现场测试中,我可以让异常生成消息来通知我异常被捕获。


1
我曾经同意代码占用量是一个问题,但是最近看来,闪存大小对微控制器的价格影响很小,远小于RAM大小或IO引脚数。
Wouter van Ooijen 2011年

关于动态内存的争论更为重要。我已经看到工业系统可以连续运行数周,但是诊断层(用C ++编写)会将重启时间限制为大约12个小时。
德米特里·格里戈里耶夫

6

我认为Linus Torvalds的这种反C ++声音很有趣。

C ++绝对最糟糕的功能之一是它如何使很多事情与上下文相关,这仅意味着当您查看代码时,局部视图很少会提供足够的上下文来了解正在发生的事情。

他不是在谈论嵌入式系统领域,而是在谈论Linux内核开发。对我而言,相关性来自于此:C ++需要理解更大的上下文,并且我可以学习使用一组对象模板,我不相信自己会在几个月后必须更新代码时记住它们。

(另一方面,我目前正在使用Python(不是C ++,但使用相同的OOP范例)在嵌入式设备上进行工作,这将确实存在该问题。在我的辩护中,这是一个功能强大的嵌入式系统,可以称为PC 10年前。)


5
我们可能有所不同,但是我发现打开任何一个项目我都无法立即知道发生了什么,但是如果我知道它在做什么,并且我用C编写了很好的代码,并且用C ++编写了很好的代码,那么C ++似乎总是更多明确。您仍然需要实现封装才能在C中进行良好的开发,而C ++则很容易做到。正确使用类可以使您清楚地知道接口的位置,并且可以通过对象完全处理它们。
Kortuk 2010年

完全同意封装和类。运算符的重载和继承没有那么多。
2010年

1
哈哈,是的,可以使用运算符重载来混淆代码的功能。如果有人在操作员超负荷工作,则出于明确的原因或根本没有做过。继承仅在实际情况下才使用,在某些情况下,您实际上正在做一些与父级类似的操作。我想我不会在OOP中使用每个函数。我都使用过,但是在嵌入式系统中我无法想到这样的情况。正如我认为对变量名称限制为80个字符的编译器应立即取消。
Kortuk 2010年

2
我只是
想念一下

您不是唯一的人,但是如果它运作良好且高效,我可以原谅。
Kortuk 2010年

6

我认为其他答案对于正反两方面和决策因素都有很好的论据,所以我只想总结一下并添加一些评论。

对于小型微控制器(8位),没有办法。您只是要伤害自己,没有收获,您将浪费太多资源。

对于具有不错操作系统的高端微控制器(例如32位,10或100 MB的MB用于内存和存储),这是完全可以的,而且我敢说,甚至推荐使用。

所以问题是:边界在哪里?

我不确定,但是一旦我用C ++开发了一个具有1 MB RAM和1 MB存储空间的16位uC系统,便后悔了。是的,它确实有效,但是我所付出的额外工作并不值得。我必须使它适应,确保诸如异常之类的东西不会产生泄漏(OS + RTL支持非常错误且不可靠)。而且,OO应用程序通常会做很多小的分配,而这些应用程序的堆开销又是另一个噩梦。

有了这些经验,我会为以后的项目假设我只会在至少16位,RAM和存储空间至少为16 MB的系统中选择C ++。这是一个任意限制,并且可能会根据应用程序类型,编码样式和习惯用法等因素而有所不同。但是,请注意,我建议使用类似的方法。


2
在这里我不得不不同意,由于系统资源的原因,C ++可以被接受并不是突然的事情,良好的设计实践可以使C ++的足迹保持在C的足迹之下。这导致带有OOP设计的代码占用相同的空间。编写不佳的C可能同样糟糕。
Kortuk 2011年

1
好吧,这取决于应用程序的大小以及您使用多少需要更多空间的某些功能(例如模板和异常)。但是个人而言,我宁愿使用C而不是将自己局限于受限的C ++。但是即使那样,您仍然会承担更大的RTL,虚拟方法重击,构造函数/析构函数链调用的开销……通过仔细的编码可以减轻这些影响,但是却失去了使用C ++,抽象和高层次的观点。
fceconel 2011年

4

C ++的某些功能在嵌入式系统中很有用。还有其他一些东西,例如例外情况,可能会很昂贵,而且其代价可能并不总是显而易见的。

如果我有德鲁特,那将是一种流行的语言,它结合了两种语言的优点,并包含了两种语言所缺乏的一些功能;一些供应商提供了一些这样的功能,但是没有标准。我想看几件事:

  1. 异常处理有点像Java,在Java中必须声明可能引发或泄漏异常的函数。尽管从编程的角度来看,对此类声明的要求可能会有些烦人,但如果函数可以成功返回任意整数,但也可能失败,则可以提高代码的清晰度。许多平台可以通过例如在寄存器中具有返回值以及在进位标志中具有成功/失败指示来廉价地以代码方式处理此问题。
  2. 仅重载静态和内联函数;我的理解是C的标准机构避免了函数重载,从而避免了名称修饰。仅允许重载静态函数和内联函数将避免该问题,并且将提供重载外部函数的99.9%的好处(因为.h文件可以根据名称不同的外部函数定义内联重载)
  3. 重载任意或特定的可在编译时解析的常量参数值。当使用任何常量值传递时,某些函数可能非常有效地内联,但是如果传递变量,则它们的内联效果很差。其他时候,如果值恒定则可能是优化的代码,如果不是则可能是悲观的。例如:
    内联void copy_uint32s(uint32_t * dest,const uint32_t * src,__ is_const int n)
    {
      如果(n <= 0)返回;
      否则,如果(n == 1){dest [0] = src [0];}
      否则(n == 2){dest [0] = src [0]; dest [1] = src [1];}
      否则,如果(n == 3){dest [0] = src [0]; dest [1] = src [1]; dest [2] = src [2];}
      否则,如果(n == 4){dest [0] = src [0]; dest [1] = src [1]; dest [2] = src [2]; dest [3] = src [3];}
      否则memcpy((void *)dest,(const void *)src,n * sizeof(* src));
    }
    
    如果可以在编译时评估“ n”,那么上面的代码将比对memcpy的调用更有效,但是如果不能在编译时评估“ n”,则生成的代码将比简单地执行的代码更大,更慢。叫做memcpy。

我知道C ++之父不太热衷于仅嵌入式版本的C ++,但我认为与仅使用C相比,它可以提供一些相当大的改进。

有人知道是否正在针对任何类型的标准考虑上述内容吗?



@Joby Taffey:我想我编辑了我的文章,以免提及C ++的创建者并不热衷于嵌入式子集。我知道有人在努力,但据我了解,他们还没有真正做到所有。我确实认为肯定会使用适用于8位处理器的标准化语言,并且如上所述的功能在任何平台上似乎都很有用。您是否听说过提供上述#3之类的语言?这似乎很有用,但是我从未见过任何语言能够提供它。
超级猫

“ C ++之父”对嵌入式系统编程经验为零,那么为什么有人会关心他的意见呢?
隆丁

@伦丁:事实上,一些有影响力的人的确在乎他在各种事务上的观点,这似乎是其他人这样做的原因。我在想,自从我在上面撰写上述内容以来,模板的功能不断增强,可能会增加一些新的可能性,使其可以基于在编译时可以解析的常量来进行重载,尽管与支持将此类东西作为编译对象相比,这种方法要干净得多,时间功能(据我了解,可以指定一个模板,该模板应按顺序尝试各种操作,并采用不会失败的第一个模板...
supercat

...但是这将要求编译器花费大量的精力来编译潜在的替换,然后最终将其丢弃。能够更清楚地说“如果这是一个常数,请执行此操作;否则请执行此操作”而没有任何“错误的开始”,这似乎是一种更干净的方法。
超级猫

3

C ++不仅仅是一种编程语言:

a)是一种“更好的” C b)是一种面向对象的语言c)这是一种允许我们编写通用程序的语言

尽管所有这些功能都可以单独使用,但是同时使用其中三个功能可以达到最佳效果。但是,如果您只选择其中之一,嵌入式软件的质量将会提高。

a)是“更好”的C

C ++是一种强大的类型化语言。比C强。您的程序将受益于此功能。

有些人害怕指针。C ++包括参考。重载功能。

值得一提:较大或较慢的程序都不会产生这些功能。

b)这是一种面向对象的语言

有人在这篇文章中说,在微控制器中抽象机器不是一个好主意。错误!我们所有人,嵌入式工程师,总是将计算机抽象化,只是带有C ++的其他语法。我看到的这个问题是,有些程序员不习惯于在对象中思考,这就是他们看不到OOP的好处。

每当您准备使用微控制器的外设时,很可能已经以设备驱动程序的形式(从您本人或第三方)为我们抽象了该外设。如我之前所说,该驱动程序使用C语法,如以下示例所示(直接取自NXP LPC1114示例):

/ *在TICKRATE_HZ设置计时器以进行匹配和中断* /

Chip_TIMER_Reset(LPC_TIMER32_0);

Chip_TIMER_MatchEnableInt(LPC_TIMER32_0,1);

Chip_TIMER_SetMatch(LPC_TIMER32_0,1,(timerFreq / TICKRATE_HZ2));

Chip_TIMER_ResetOnMatchEnable(LPC_TIMER32_0,1);

Chip_TIMER_Enable(LPC_TIMER32_0);

您看到抽象了吗?因此,当出于相同目的使用C ++时,通过C ++的抽象和封装机制可以将抽象提升到一个新的水平,而成本却为零!

c)这是一种允许我们编写通用程序的语言

通用程序是通过模板实现的,并且模板对于我们的程序也没有任何费用。

此外,静态多态性是通过模板实现的。

虚拟方法,RTTI和异常。

使用虚拟方法时存在一个折衷:更好的软件与性能的损失。但是,请记住,动态绑定可能使用虚拟表(函数指针数组)来实现。我已经在C中做了很多次(甚至是定期进行),所以我看不到使用虚拟方法的弊端。而且,C ++中的虚拟方法更加优雅。

最后,关于RTTI和异常的建议:不要在嵌入式系统中使用它们。不惜一切代价避免它们!


2

我的背景是实时的(嵌入式,MCU,PC,Unix,其他)。安全至关重要。我向STL介绍了以前的雇主。我不再这样做了。

一些火焰含量

C ++是否适合嵌入式系统?

嗯 C ++是编写的痛苦,也是维护的痛苦。C +还可以(不要使用某些功能)

微控制器中的C ++?实时操作系统?烤面包机?嵌入式PC?

我再次说梅。C +并不太糟糕,但是ADA却不那么痛苦(这实际上是在说些什么)。如果像我这样幸运的话,您可以使用嵌入式Java。检查数组访问,并且没有指针算法,因此代码非常可靠。嵌入式Java中的垃圾收集器并不是最高优先级,并且具有一定范围的内存和对象重用,因此,设计良好的代码可以在没有GC的情况下永久运行。

OOP在微控制器上有用吗?

当然可以 UART是一个对象..... DMAC是一个对象...

对象状态机非常简单。

C ++是否会将程序员从硬件中移走太远以至于效率不高?

除非它是PDP-11,否则就不是您的CPU。C ++最初是C语言的预处理器,因此Bjarne Stroustrup不会因为在AT&T上进行缓慢的Simula仿真而被嘲笑。C ++不是您的CPU。

去获得一个运行Java字节码的MCU。Java程序。嘲笑C家伙。

Arduino的C ++(没有动态内存管理,模板,异常)是否应被视为“真正的C ++”?

不。就像所有针对MCU的混蛋C编译器一样。

第四,嵌入式Java或嵌入式ADA已标准化。其他一切都是悲伤。


2
找到支持Java的微控制器容易吗?我认为这会极大地限制选择。您对性能下降有何经验(由于在uC中您通常不会拥有JIT)?GC不可预测性对实时系统的影响如何?
fceconel 2011年

2
有哪些支持嵌入式Java的MCU?
J. Polfer 2011年

对于初学者,请访问www.ajile.com。
Tim Williscroft 2011年

+1为Ada。在包括Arduinos在内的嵌入式系统中,它有很多好处。
Brian Drummond 2012年

用c编写的用于micros的便携式Java VM是开源的。dmitry.co/index.php?p=./04.Thoughts/...
蒂姆Williscroft

-2

嵌入式系统旨在执行某些特定任务,而不是用作执行多个任务的通用计算机。嵌入式系统是计算机硬件和软件的组合。C是所有现代语言的母亲。这是一个较低级别的语言,但功能全面,并且可以处理所有类型的硬件。因此,C / C ++是开发嵌入式系统软件的最佳选择,它非常适合每个嵌入式系统。众所周知,C是一种开发语言。操作系统UNIX是用C编写的。由于成功的软件开发经常是为给定的项目选择最佳语言,因此令人惊讶地发现C / C ++语言已证明自己适用于8位和64位处理器; 在具有字节,千字节和兆字节内存的系统中。C具有处理器独立性的优势,这使程序员可以专注于算法和应用程序,而不是特定处理器体系结构的细节。但是,这些优点中的许多优点同样适用于其他高级语言。但是C / C ++成功了,而其他许多语言却大都失败了?


6
我真的不确定这会增加讨论的内容。
戴夫·特威德

-3

<rant>

我认为C ++首先是一种糟糕的语言。如果要使用OOP,请编写Java程序。C ++不会执行OOP范例,因为直接内存访问完全在(滥用)能力范围内。

如果您有MCU,那么您所谈论的闪存很可能少于100kB。您想使用一种对内存的抽象为的语言进行编程:当我声明一个变量或数组时,它获取内存,句点;除非在极少数情况下在程序启动期间进行一次调用,否则应在嵌入式软件中或多或少禁止使用malloc(在C ++中为“ new”关键字)。

糟糕,在嵌入式编程中,有时(经常)C不够底层,您需要执行一些操作,例如将变量分配给寄存器,并编写内联汇编以加强中断服务例程(ISR)。诸如“ volatile”之类的关键字变得非常重要,难以理解。您花费大量时间在位级别而不是对象级别上操作内存。

您为什么要自欺欺人地认为事情比实际上简单?

</ rant>


我的问题很简单,如果我已经完全开发了用于处理接口的驱动程序,那么为什么我想知道为控制USART1而编写的驱动程序的复杂性。
Kortuk 2010年

1
我没有否决您的意见,但我想指出,C ++无需强制执行OOP,只需提供执行此操作的工具即可。实施好的编码标准是开发人员的工作。如果该语言使它变得更简单,则可以提供帮助,但是该语言永远不会自己做到。C在某些情况下可能不可读。
Kortuk 2010年

1
所有语言对某些事物都有好处。C ++速度很快。如果做得好,OOP将使多个开发人员可以更轻松地并行工作并为未知的人编写代码。我认为这就是为什么它在游戏开发中具有如此大的吸引力。
Toby Jaffey 2010年

1
是的我同意。我之所以在嵌入式世界中看到它,是因为大量的特性和功能被添加到了已经存在的许多不同系统中,并且正在开发新的系统。项目越来越大。我们要么花费更长的时间来开发它们,要么开始应用并扭曲CS世界已经在PC上完成的工作。
Kortuk 2010年

5
另一个不正确理解C ++的人。它总是让我感到惊讶。
Rocketmagnet '02
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.