有没有任何理由使用C而不是C ++进行embedded式开发?
题
我的硬件C ++和C89上有两个编译器
我正在考虑使用C ++的类,但没有多态(避免vtable)。 我想使用C ++的主要原因是:
- 我更喜欢使用“内联”函数而不是macros定义。
- 我想使用命名空间,因为我前缀杂乱的代码。
- 我看到C ++有点types更安全,主要是因为模板和详细的转换。
- 我真的很喜欢重载的函数和构造函数(用于自动投射)。
在开发非常有限的硬件(RAM的4kb)时,您是否看到有任何理由坚持使用C89?
结论
谢谢你的回答,他们真的很有帮助!
我认为这个主题通过,我会坚持与C主要是因为:
- 在C中预测实际的代码比较容易,如果你只有4kb的内存,这是非常重要的。
- 我的团队主要由C开发人员组成,所以先进的C ++function不会经常使用。
- 我find了一种在C编译器(C89)中内联函数的方法。
你提供了这么多好的答案,很难接受一个答案。 不幸的是,我不能创build一个wiki并接受它,所以我会select一个让我最想得到的答案。
在C ++上使用C的两个原因:
- 对于很多embedded式处理器来说,要么没有C ++编译器,要么为此付出额外的代价。
- 我的经验是,很大一部分embedded式软件工程师很less或没有C ++经验 – 或者是因为(1),或者是因为它往往不被教授在电子工程学学位上 – 所以最好坚持他们知道什么
另外,最初的问题和一些评论提到了4 Kb的RAM 。 对于典型的embedded式处理器,RAM的数量(大部分)与代码大小无关,因为代码存储在闪存中,并从闪存运行。
当然,代码存储空间的数量是需要记住的,但是随着市场上新的,更加广阔的处理器的出现,这个问题不像以前那样只是对成本敏感的项目。
关于使用embedded式系统使用的C ++子集:现在有一个MISRA C ++标准,值得一看。
编辑:另请参见这个问题 ,这导致了关于embedded式系统的C vs C ++的争论。
对于一个非常资源有限的目标,例如4KB的RAM,我会在付出很多努力之前先testing一些样本,而这些努力不能简单地移植回纯粹的ANSI C实现中。
embedded式C ++工作组的确提出了一个标准的子集和标准库的标准子集。 不幸的是,当C用户日志去世时,我忘记了这一努力。 看起来维基百科上有一篇文章, 委员会依然存在。
在embedded式环境中,你必须小心内存分配。 为了执行这种保护,您可能需要将全局operator new()
及其朋友定义为不能链接的东西,以便您知道它不被使用。 另一方面,如果使用稳定的,线程安全的和延迟保证的分配scheme,安置new
的东西可能会成为你的朋友。
内联函数不会引起太多问题,除非它们足够大,以至于它们本来应该是真正的函数。 当然,他们替代的macros也有同样的问题。
模板也可能不会导致问题,除非它们的实例运行失控。 对于你使用的任何模板,审计你生成的代码(链接图可能有足够的线索),以确保只有你打算使用的实例发生。
另一个可能出现的问题是与debugging器的兼容性。 对于其他可用的硬件debugging器来说,与原始源代码交互的支持是非常有限的。 如果你真的需要在程序集中进行debugging,那么C ++中有趣的名字会增加额外的混乱。
RTTI,dynamic强制转换,多重inheritance,繁重的多态性和exception都会带来一些运行时成本。 如果使用这些function,那么这些function中的一部分成本会超过整个程序,而其他function则会增加需要它们的类的重量。 了解其中的差异,并在充分了解至less粗略的成本/收益分析的情况下明智地select高级function。
在一个小型的embedded式环境中,您将直接链接到实时内核,或直接在硬件上运行。 无论哪种方式,您都需要确保运行时启动代码正确处理特定于C ++的启动任务。 这可能与确保使用正确的链接器选项一样简单,但由于通常将源直接控制在上电复位入口点上,因此可能需要对其进行审计,以确保它完成所有工作。 例如,在我工作的ColdFire平台上,随CRT0.S模块一起提供的开发工具有C ++初始值设定项,但是注释掉了。 如果我直接从盒子里使用它,我会被全部的构造器从来没有运行过的对象迷惑过。
另外,在embedded式环境中,通常有必要在硬件设备被使用之前初始化硬件设备,如果没有操作系统,也没有引导加载程序,那么这就是你的代码。 你需要记住全局对象的构造函数是在 main()
被调用之前运行的,所以你需要修改你的本地CRT0.S(或者它的等价物),以便在调用全局构造函数之前完成硬件初始化。 显然, main()
的顶部太晚了。
不可以。在进行embedded式开发时,可以避免任何可能导致问题的C ++语言function(运行时多态性,RTTI等)。 有一个embedded式C ++开发人员社区(我记得在旧的C / C ++用户期刊中使用C ++的embedded式开发人员阅读了专栏),如果select不好,我不能想象他们会很有发言权。
C ++性能技术报告是这类事情的一个很好的指导。 请注意,它有一个关于embedded式编程问题的部分!
另外,++上提到了Embedded C ++中的答案。 这个标准对我的口味并不是100%,但是在决定你可能放弃哪些C ++部分的时候,它是一个很好的参考。
在为小平台编程时,我们禁用了exception和RTTI,避免了虚拟inheritance,并且密切关注了我们所处的虚拟函数的数量。
不过,你的朋友是链接器映射:经常检查,你会很快发现代码和静态内存膨胀的来源。
之后,标准的dynamic内存使用注意事项适用于:在您提到的那个环境中,您可能根本不想使用dynamic分配。 有时你可以使用内存池来获得小的dynamic分配,或者在你预先分配一个块并在之后抛出所有内容的情况下,使用“基于帧”的分配。
我推荐使用C ++编译器,但是限制你使用C ++特有的function。 你可以在C ++中编写C程序(在C ++中包含C运行时,尽pipe在大多数embedded式应用程序中,你仍然不使用标准库)。
你可以继续使用C ++类等
- 限制你使用虚拟function(如你所说)
- 限制你使用模板
- 对于embedded式平台,您需要覆盖运营商新的和/或使用位置新的内存分配。
作为一名固件/embedded式系统工程师,我可以告诉大家一些为什么C仍然是C ++的首选,是的,我很stream利。
1)我们开发的一些目标对于代码和数据都有64kB的RAM,所以你必须确保每个字节的计数,是的,我已经处理了代码优化,以节省4个字节,花费我2个小时,这是在2008年。
2)每个C库函数在被允许进入最终代码之前都要进行审查,因为大小限制,所以我们更喜欢不使用鸿沟(没有硬件分割器,所以需要一个大型的库),malloc(因为我们没有堆,所有内存都是从512字节块的数据缓冲区中分配的,并且必须进行代码审查)或其他面向对象的实践,这些实践会带来很大的代价。 请记住,您使用的每个库函数都是有效的。
3)有没有听说过覆盖? 你有这么less的代码空间,有时你不得不用另一套代码交换事情。 如果您调用库函数,则库函数必须驻留。 如果你只在覆盖函数中使用它,那么就是依靠太多面向对象的方法浪费了很多空间。 所以,不要假设任何C库函数,更不要说C ++被接受了。
4)由于有限的硬件devise(即以某种方式连接的ECC引擎)或者为了应对硬件缺陷,所以需要铸造甚至包装(其中未alignment的数据结构越过字边界)。 你不能假设太多,所以为什么对象定位太多?
5)最坏的情况:消除一些面向对象的方法将迫使开发人员在使用可能爆炸的资源之前进行思考(即在堆栈上而不是从数据缓冲区中分配512字节),并防止某些潜在的最坏情况没有一起testing或消除整个代码path。
6)我们使用了很多抽象来保持硬件不受软件限制,并且使得代码尽可能便携,并且仿真友好。 硬件访问必须包装在一个macros或者内联函数中,这些函数是在不同平台之间进行有条件编译的,数据types必须按照字节大小而不是目标指定,不允许直接使用指针(因为某些平台假定内存映射I / O是与数据存储器相同)等。
我可以多想一想,但你明白了。 美国的固件厂商确实有面向对象的培训,但embedded式系统的任务可以是硬件导向的,低层次的,本质上并不是高层次的或抽象的。
顺便说一句,我一直在使用源代码控制的每一个固件工作,我不知道你从哪里得到这个想法。
SanDisk的一些固件。
我个人的偏好是C,因为:
- 我知道每一行代码在做什么(和成本)
- 我不太了解C ++,知道每行代码在做什么(和成本)
为什么人们这样说呢? 除非检查asm输出,否则不知道C的每一行都在做什么。 C ++也一样。
例如,这个无辜的陈述产生了什么样的ASM:
a[i] = b[j] * c[k];
它看起来相当无辜,但是一个基于gcc的编译器生成了一个8位微的asm
CLRF 0x1f, ACCESS RLCF 0xfdb, W, ACCESS ANDLW 0xfe RLCF 0x1f, F, ACCESS MOVWF 0x1e, ACCESS MOVLW 0xf9 MOVF 0xfdb, W, ACCESS ADDWF 0x1e, W, ACCESS MOVWF 0xfe9, ACCESS MOVLW 0xfa MOVF 0xfdb, W, ACCESS ADDWFC 0x1f, W, ACCESS MOVWF 0xfea, ACCESS MOVFF 0xfee, 0x1c NOP MOVFF 0xfef, 0x1d NOP MOVLW 0x1 CLRF 0x1b, ACCESS RLCF 0xfdb, W, ACCESS ANDLW 0xfe RLCF 0x1b, F, ACCESS MOVWF 0x1a, ACCESS MOVLW 0xfb MOVF 0xfdb, W, ACCESS ADDWF 0x1a, W, ACCESS MOVWF 0xfe9, ACCESS MOVLW 0xfc MOVF 0xfdb, W, ACCESS ADDWFC 0x1b, W, ACCESS MOVWF 0xfea, ACCESS MOVFF 0xfee, 0x18 NOP MOVFF 0xfef, 0x19 NOP MOVFF 0x18, 0x8 NOP MOVFF 0x19, 0x9 NOP MOVFF 0x1c, 0xd NOP MOVFF 0x1d, 0xe NOP CALL 0x2142, 0 NOP MOVFF 0x6, 0x16 NOP MOVFF 0x7, 0x17 NOP CLRF 0x15, ACCESS RLCF 0xfdf, W, ACCESS ANDLW 0xfe RLCF 0x15, F, ACCESS MOVWF 0x14, ACCESS MOVLW 0xfd MOVF 0xfdb, W, ACCESS ADDWF 0x14, W, ACCESS MOVWF 0xfe9, ACCESS MOVLW 0xfe MOVF 0xfdb, W, ACCESS ADDWFC 0x15, W, ACCESS MOVWF 0xfea, ACCESS MOVFF 0x16, 0xfee NOP MOVFF 0x17, 0xfed NOP
产生的指令数量大量依赖于:
- a,b和c的大小。
- 无论这些指针是存储在堆栈上还是全局的
- 无论i,j和k在堆栈中还是全局的
在微小的embedded式世界中,情况尤其如此,因为处理器只是没有设置处理C.所以我的答案是C和C ++一样糟糕,除非你总是检查asm输出,在这种情况下,他们是一样好的彼此。
雨果
我听说有些人更喜欢Cembedded式工作,因为这个事实比较简单,因此更容易预测将要生成的实际代码。
我个人会认为编写C风格的C ++(使用types安全模板)会给你很多好处,但是我看不到任何真正的原因。
我没有理由使用C而不是C ++。 无论你在C中做什么,你都可以在C ++中完成。 如果你想避免VMT的开销,不要使用虚拟方法和多态。
不过,C ++可以提供一些非常有用的习惯用法,没有开销。 我最喜欢的是RAII。 在内存或性能方面,类不需要昂贵…
我在IAR Workbench上为ARM7embedded式平台编写了一些代码。 我强烈build议依靠模板来进行编译时优化和path预测。 避免像瘟疫一样的dynamic投射。 按照Andrei Alexandrescu的书“ 现代C ++devise”的规定,使用特性/策略是有利的。
我知道,这可能很难学,但我也确信你的产品将会受益于这种方法。
一个很好的理由,有时候唯一的原因是特定的embedded式系统仍然没有C ++编译器。 例如, Microchip PIC微控制器就是这种情况。 他们很容易编写,他们有一个免费的C编译器(实际上,是C的一个轻微的变体),但没有C ++编译器。
对于一个限制为4K内存的系统,我会使用C,而不是C ++,这样就可以确保看到正在发生的一切。 使用C ++的一点是,使用更多的资源(包括CPU和内存)非常容易,而不是像看上代码那样。 (哦,我只是创build另一个BlerfObject来做到这一点…哎呀!内存不足!)
你可以用C ++来完成,就像已经提到的那样(没有RTTI,没有vtables等等),但是你会花费很多时间来确保你的C ++用法不会像你在C 。
我个人用4kb的内存,我会说你没有得到更多的C ++的里程,所以只要select一个似乎是最好的编译器/运行时组合的工作,因为语言可能不会太重要。
请注意,它也不是全部关于语言,因为图书馆也很重要。 通常C库的最小尺寸略小,但是我可以想象一个针对embedded式开发的C ++库会被削减,所以一定要testing一下。
人的头脑通过尽可能多的评估来处理复杂性,然后决定重点是什么,重点放在什么,丢弃或贬低其余部分。 这是营销背后的全部基础,主要是图标。
为了克服这种倾向,我更喜欢C到C ++,因为它迫使你思考你的代码,以及它如何更紧密地与硬件交互 – 无情地closures。
从长期的经验来看,我相信C强迫你提出更好的解决问题的办法,部分原因是不要让你浪费大量时间来满足约束条件,一些编译器作者的想法是一个好主意,或者搞清楚“底下”是怎么回事。
在这种情况下,像C这样的低级语言,你花费了大量的时间专注于硬件和构build良好的数据结构/algorithm包,而高级语言花费了大量的时间,不知道在那里发生了什么,以及为什么你不能在你的特定环境和环境中做一些完全合理的事情。 打击你的编译器提交(强打字是最严重的罪犯)不是一个生产性的使用时间。
我很可能适合程序员的模子 – 我喜欢控制。 在我看来,这不是程序员的个性缺陷。 控制是我们得到的报酬。 更具体地说,FLAWLESSLY控制。 C给你比C ++更多的控制权。
有人说C编译器可以生成更高效的代码,因为它们不需要支持先进的C ++特性,因此可以在优化中更积极。
当然,在这种情况下,您可能需要将两个特定的编译器进行testing。
C赢得了可移植性 – 因为它在语言规范上不太模糊; 因此在不同编译器之间提供更好的可移植性和灵活性(更less的麻烦)。
如果您不打算利用C ++function来满足需求,那么请与C.
在开发非常有限的硬件(RAM的4kb)时,您是否有理由坚持使用C89?
就个人而言,当涉及到embedded式应用程序(当我说embedded式,我不是指今天的WinCE,iPhone等..臃肿的embedded式设备)。 我指的是资源有限的设备。 我更喜欢C,尽pipe我也用C ++很多。
例如,你所说的设备有4kb的内存,正因为如此,我不会考虑C ++。 当然,你也许可以使用C ++devise一些小的东西,并限制你在应用程序中的使用,就像其他文章所build议的一样,但是C ++“可能”最终会使你的应用程序复杂化/膨胀。
你要静态链接吗? 你可能想要比较静态的一个虚拟应用程序使用C + + C。 这可能会导致你考虑C代替。 另一方面,如果你能够在你的内存需求中构build一个C ++应用程序,那就去做吧。
恕我直言,一般来说,在embedded式应用程序,我喜欢知道正在发生的一切。 谁在使用内存/系统资源,多less,为什么? 他们什么时候解放他们?
在开发具有X资源量,cpu,内存等目标的时候,我会尽量保持使用这些资源的底层,因为你永远不知道将来会有什么需求,因此你需要添加更多的代码给项目被认为是一个简单的小应用程序,但最终变得更大了。
我的select通常取决于我们决定使用的C库,它是根据设备需要做的事情来select的。 所以,9/10次..它最终是uclibc或newlib和C.我们使用的内核也是一个很大的影响,或者如果我们正在编写我们自己的内核。
它也是一个共同点的select。 大多数优秀的C程序员使用C ++没有任何问题(尽pipe很多人抱怨他们使用C ++的时间),但是我还没有发现相反的情况(以我的经验)。
在我们正在开发的一个项目中(涉及内核),大多数事情都是用C语言完成的,但是一个小型的networking堆栈是用C ++来实现的,因为使用C ++来实现联网只是一个简单的问题。
最终的结果是,该设备将工作,并通过验收testing或不会。 如果你可以用x语言在xx堆栈和yy堆约束中实现foo,那就去做吧,使用任何可以提高生产力的方法。
我个人的偏好是C,因为:
- 我知道每一行代码在做什么(和成本)
- 我不太了解C ++,知道每行代码在做什么(和成本)
是的,我对C ++很满意,但是我不知道它和我做的标准C一样。
现在,如果你能说出相反的话,那就用你所知道的:)如果它能正常工作,通过testing等等。那有什么问题?
你有多lessROM / FLASH?
RAM的4kB仍然意味着有数百KB的FLASH存储实际的代码和静态数据。 这个大小的RAM往往只是为了variables,如果你对这些小心的话,你可以在代码行中放入一个很大的程序。
但是,由于对象的运行时间结构规则,C ++往往会使代码和数据更容易放入FLASH中。 在C中,一个常量struct可以很容易地放入FLASH存储器,并作为一个硬件常数对象来访问。 在C ++中,一个常量对象需要编译器在编译时对构造函数进行求值,我认为这仍然超出了C ++编译器的能力(理论上,你可以这样做,但在实践中很难做到) 。
所以在一个“小RAM”,“大FLASH”的环境下,我会随着C随时去。 请注意,一个很好的中间select,我C99具有非基于类的代码大部分好的C ++function。
一般来说没有。 C ++是C的一个超集。对于新项目来说尤其如此。
在避免C ++构造方面,你正处于正确的轨道上,在cpu时间和内存占用方面可能会很昂贵。
请注意,像多态这样的东西可能非常有价值 – 基本上是函数指针。 如果你觉得你需要他们,明智地使用他们。
另外,良好的(精心devise的)exception处理可以使您的embedded式应用程序比使用传统错误代码处理事物的应用程序更可靠。
selectC恕我直言的唯一原因是如果您的平台的C ++编译器不是一个好的形状(越野车,穷人优化等)。
你已经在C99内联了。 也许你喜欢ctors,但获得dtors的权利可以是混乱的。 如果剩下的唯一不使用C的原因是命名空间,我会坚持C89。 这是因为您可能希望将其移植到一个稍微不同的embedded式平台。 您可能稍后开始用C ++编写相同的代码。 但要注意以下几点,C ++不是C的超集。我知道你说过你有一个C89编译器,但是这个C ++与C99的比较无论如何,因为第一个例子对于自K&R以来的任何C都是真实的。
C中的sizeof'a' > 1,而不是C ++。 在C中你有VLA可变长度数组。 例如: func(int i){int a [i] 。 在C中你有VAM可变数组成员。 例如: struct {int b; int m [];} 。
只是想说没有“无限”资源的系统。 这个世界上的所有东西都是有限的,无论是ASM,C,JAVA还是JavaScript,每个应用程序都应该考虑资源的使用。 分配几Mbs的“虚拟”可以让iPhone 7,Pixel等设备非常stream畅。 不pipe你有4kb还是40GB。
但从另一个方面来反对资源浪费 – 这是一个节省这些资源的时间。 如果在C中写一个简单的东西需要花费一个星期的时间来保存几个tick和几个字节,而不是使用已经实现,testing和分发的C ++。 何必? 就像买一个USB集线器一样。 是的,你可以自己做,但会更好吗? 更可靠? 如果你算你的时间便宜?
只是一个想法 – 即使从你的sockets权力并不是无限的。 试着研究它从哪里来,你会看到它主要是从燃烧的东西。 能量和物质的规律仍然是有效的:没有物质或能量出现或消失,而是转化。
对于内存分配问题,我可以推荐使用Quantum Platform及其状态机方法,因为它会在初始化时分配所需的所有内容。 这也有助于缓解争用问题。
该产品在C和C ++上运行。
游戏程序员的书C ++有基于C ++特征的代码大小增加的信息。
这取决于编译器。
并不是所有的embedded式编译器都能实现所有的C ++,即使他们这样做,他们可能不善于避免代码膨胀(这对模板来说总是有风险的)。 用几个较小的程序进行testing,看看是否遇到任何问题。
但是给定一个好的编译器,不,没有理由不使用C ++。
我刚刚发现了一个如何使用ISO C ++进行embedded式开发的例子,这个例子对于那些在使用C ++或C时作出决定的人来说可能是有趣的。
它由Bjarne Stroustrup 在他的主页上提供 :
要了解ISO C ++如何用于严肃的embedded式系统编程,请参阅JSF飞行器C ++编码标准 。
不同的答案张贴到问题的不同方面:
“的malloc”
以前的一些回复谈到这一点。 为什么你甚至认为这个电话存在? 对于一个真正的小平台,malloc往往不可用,或者绝对可选。 实现dynamic内存分配在你的系统底部有一个RTOS的时候往往是有意义的 – 但在那之前,这是纯粹的危险。
没有它,你会变得非常远。 想想所有旧的FORTRAN程序甚至没有适当的本地variables堆栈…
在这样一个有限的系统上。 只要去汇编。 让您完全掌控各个方面,并且不会带来任何开销。
Probably a lot faster too since a lot of embedded compilers are not the best optimizers (especially if one compares it to state of the art compilers like the ones we have for the desktop (intel, visual studio, etc))
"yeah yeah…but c is re-usable and…". On such a limited system, chances are you won't re-use much of that code on a different system anyway. On the same system, assembler is just as re-usable.