问题

我的硬件上有两个编译器 C++ 和 C89

我正在考虑将 C++ 与类一起使用,但不使用多态性(以避免虚函数表)。我想使用 C++ 的主要原因是:

  • 我更喜欢使用“内联”函数而不是宏定义。
  • 我想使用命名空间,因为我的前缀会使代码变得混乱。
  • 我认为 C++ 的类型更安全,主要是因为模板和详细的转换。
  • 我真的很喜欢重载函数和构造函数(用于自动转换)。

在针对非常有限的硬件(4kb RAM)进行开发时,您认为有什么理由坚持使用 C89 吗?

结论

感谢您的回答,他们真的很有帮助!

我仔细考虑了这个主题,我会坚持使用 C,主要是因为:

  1. 预测 C 中的实际代码会更容易,如果您只有 4kb 的 RAM,这一点非常重要。
  2. 我的团队主要由 C 开发人员组成,因此不会经常使用高级 C++ 功能。
  3. 我找到了一种在 C 编译器 (C89) 中内联函数的方法。

很难接受一个答案,因为您提供了这么多好的答案。不幸的是,我无法创建一个维基并接受它,所以我会选择一个最让我思考的答案。

有帮助吗?

解决方案

使用 C 而不是 C++ 的两个原因:

  1. 对于很多嵌入式处理器来说,要么没有C++编译器,要么需要额外付费。
  2. 我的经验是,很大一部分嵌入式软件工程师很少或根本没有 C++ 经验——要么是因为 (1),要么是因为它往往不会在电子工程学位上教授——所以最好坚持使用他们知道什么。

另外,最初的问题和一些评论提到了 4 Kb 内存. 。对于典型的嵌入式处理器,RAM 的数量(大部分)与代码大小无关,因为代码是在闪存中存储和运行的。

当然,代码存储空间的大小是需要记住的事情,但随着市场上出现新的、容量更大的处理器,除了对成本最敏感的项目之外,对于所有项目来说,这都不再是一个问题了。

关于将 C++ 子集用于嵌入式系统:现在有一个 米斯拉 C++ 标准,可能值得一看。

编辑: 也可以看看 这个问题, ,这引发了关于嵌入式系统的 C 与 C++ 的争论。

其他提示

为一个 非常 对于资源受限的目标(例如 4KB RAM),我会在投入大量无法轻松移植回纯 ANSI C 实现的工作之前使用一些示例进行测试。

嵌入式 C++ 工作组确实提出了该语言的标准子集以及与其配套的标准库的标准子集。不幸的是,当 C User's Journal 消失后,我就失去了对这一努力的追踪。好像有一篇文章在 维基百科, ,并且 委员会 仍然存在。

在嵌入式环境中,您确实必须小心内存分配。为了加强这种护理,您可能需要定义全局 operator new() 以及它的朋友甚至无法链接到的东西,这样你就知道它没有被使用。放置 new 另一方面,如果明智地与稳定、线程安全和延迟保证的分配方案一起使用,它可能会成为你的朋友。

内联函数不会造成太大问题,除非它们足够大,以至于它们一开始就应该是真正的函数。当然,他们替换的宏也有同样的问题。

模板也可能不会引起问题,除非它们的实例化失控。对于您使用的任何模板,请审核生成的代码(链接映射可能有足够的线索)以确保仅发生您打算使用的实例化。

可能出现的另一问题是与调试器的兼容性。对于其他可用的硬件调试器来说,对与原始源代码的交互的支持非常有限,这并不罕见。如果您实际上必须在汇编中进行调试,那么 C++ 中有趣的名称修改会给任务带来额外的混乱。

RTTI、动态转换、多重继承、重度多态性和异常在使用时都会产生一定的运行时成本。其中一些功能级别如果使用的话会花费整个程序的成本,其他功能只会增加需要它们的类的权重。了解差异,并在充分了解至少粗略的成本/效益分析的情况下明智地选择高级功能。

在小型嵌入式环境中,您将直接链接到实时内核或直接在硬件上运行。无论哪种方式,您都需要确保运行时启动代码正确处理 C++ 特定的启动杂务。这可能就像确保使用正确的链接器选项一样简单,但由于直接控制上电复位入口点的源很常见,因此您可能需要对其进行审核以确保它执行所有操作。例如,在我工作的 ColdFire 平台上,开发工具附带了一个 CRT0.S 模块,该模块包含 C++ 初始化程序,但已注释掉。如果我直接从盒子里使用它,我会对那些构造函数从未运行过的全局对象感到困惑。

另外,在嵌入式环境中,通常需要在使用硬件设备之前对其进行初始化,如果没有操作系统和引导加载程序,那么就需要您的代码来完成这些工作。您需要记住全局对象的构造函数是运行的 main() 被调用,因此您需要修改本地 CRT0.S (或其等效项)以完成硬件初始化 调用全局构造函数本身。显然,顶部 main() 太晚了。

不。在进行嵌入式开发时,可以避免任何可能导致问题的 C++ 语言功能(运行时多态性、RTTI 等)。有一个嵌入式 C++ 开发人员社区(我记得在旧的 C/C++ 用户杂志中读过使用 C++ 的嵌入式开发人员的专栏),我无法想象如果选择那么糟糕,他们会大声疾呼。

C++ 性能技术报告 是此类事情的一个很好的指南。请注意,它有一个关于嵌入式编程问题的部分!

另外,++ 在答案中提到了嵌入式 C++。该标准并不 100% 符合我的口味,但在决定您可能放弃 C++ 的哪些部分时,它是一个很好的参考。

在为小型平台编程时,我们禁用异常和 RTTI,避免虚拟继承,并密切关注我们现有的虚拟函数的数量。

不过,你的朋友是链接器映射:经常检查它,您会很快发现代码源和静态内存膨胀。

之后,适用标准动态内存使用注意事项:在像您提到的那样受限制的环境中,您可能根本不希望使用动态分配。有时,您可以使用内存池进行小型动态分配,或者使用“基于帧”的分配,即预先分配一个块,然后将整个块丢弃。

我建议使用 C++ 编译器,但限制您对 C++ 特定功能的使用。您可以像 C++ 中的 C 一样进行编程(执行 C++ 时包含 C 运行时,但在大多数嵌入式应用程序中您无论如何都不使用标准库)。

您可以继续使用 C++ 类等,只需

  • 限制您对虚拟函数的使用(正如您所说)
  • 限制模板的使用
  • 对于嵌入式平台,您需要覆盖 new 运算符和/或使用 new 布局进行内存分配。

作为一名固件/嵌入式系统工程师,我可以告诉大家为什么 C 仍然是 C++ 的第一选择的一些原因,是的,我对这两种语言都很熟悉。

1) 我们开发的一些目标有 64kB RAM 用于代码和数据,所以你必须确保每个字节计数,是的,我已经处理了代码优化以节省 4 个字节,这花了我 2 个小时,这在2008年。

2)每个C库函数在我们放入最终代码之前都会经过审查,因为大小限制,所以我们希望人们不要使用divide(没有硬件除法器,所以需要一个大的库),malloc(因为我们没有堆) ,所有内存都是从 512 字节块的数据缓冲区中分配的,并且必须进行代码审查),或其他带来较大损失的面向对象实践。请记住,您使用的每个库函数都很重要。

3)听说过“覆盖”这个词吗?您的代码空间太小,有时您必须用另一组代码交换内容。如果调用库函数,则该库函数必须是常驻的。如果您仅在覆盖函数中使用它,那么您会依赖太多面向对象的方法而浪费大量空间。因此,不要假设任何 C 库函数都会被接受,更不用说 C++ 会被接受。

4) 由于硬件设计有限(即,未对齐的数据结构跨越字边界),需要进行转换甚至打包(其中未对齐的数据结构跨越字边界)。以某种方式连接的 ECC 引擎)或应对硬件错误。你不能隐式地假设太多,那么为什么要过度面向对象呢?

5)最坏的情况:消除一些面向对象的方法将迫使开发人员在使用可能爆炸的资源(即)之前进行思考。在堆栈上分配 512 字节而不是从数据缓冲区中分配),并防止一些未经测试的潜在最坏情况,或完全消除整个代码路径。

6)我们确实使用了大量的抽象来使硬件与软件保持独立,并使代码尽可能可移植且易于模拟。硬件访问必须封装在宏或内联函数中,这些函数在不同平台之间有条件地编译,数据类型必须转换为字节大小而不是特定于目标的,不允许直接使用指针(因为某些平台假设内存映射 I/O 是与数据存储器相同)等。

我可以想到更多,但你明白了。我们的固件人员确实接受过面向对象的培训,但是嵌入式系统的任务可能是面向硬件的和低级别的,以至于它本质上不是高级的或可抽象的。

顺便说一句,我从事的每个固件工作都使用源代码控制,我不知道你从哪里得到这个想法。

-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 是在堆栈上还是全局的

在微小的嵌入式世界中尤其如此,因为处理器并未设置为处理 C。所以我的答案是 C 和 C++ 彼此一样糟糕,除非您总是检查 asm 输出,在这种情况下它们彼此一样好。

雨果

我听说有些人更喜欢用 C 语言进行嵌入式工作,因为它更简单,因此更容易预测将生成的实际代码。

我个人认为编写 C 风格的 C++(使用类型安全模板)会给你带来很多优势,而且我看不出有任何不这样做的真正理由。

我认为没有理由使用 C 而不是 C++。无论你能用 C 做什么,你也可以用 C++ 做。如果您想避免 VMT 的开销,请不要使用虚拟方法和多态性。

然而,C++ 可以提​​供一些非常有用的习惯用法,而无需任何开销。我最喜欢的之一是 RAII。类在内存或性能方面不一定昂贵......

我在 IAR Workbench 上为 ARM7 嵌入式平台编写了一些代码。我强烈建议依靠模板来进行编译时优化和路径预测。避免像瘟疫一样进行动态转换。按照安德烈·亚历山德雷斯库 (Andrei Alexandrescu) 书中的规定,利用特征/策略来发挥自己的优势, 现代 C++ 设计.

我知道,这可能很难学习,但我也确信您的产品将从这种方法中受益。

一个很好的原因,有时也是唯一的原因是,仍然没有针对特定嵌入式系统的 C++ 编译器。例如,情况就是如此 微芯片PIC 微控制器。它们非常容易编写,并且有一个免费的 C 编译器(实际上是 C 的一个轻微变体),但目前还没有 C++ 编译器。

对于限制为 4K 内存的系统,我会使用 C,而不是 C++,这样您就可以确保看到正在发生的一切。C++ 的问题在于,使用比看起来代码多得多的资源(CPU 和内存)非常容易。(哦,我将创建另一个 BlerfObject 来做到这一点......哎呀!内存不足!)

正如已经提到的,您可以在 C++ 中执行此操作(没有 RTTI、没有 vtable 等),但是您将花费与在 C 中执行等效操作一样多的时间来确保 C++ 的使用不会远离您。

人类大脑通过尽可能多地评估来处理复杂性,然后决定重点关注什么,并丢弃或贬低其余的。这是营销中品牌(主要是图标)背后的全部基础。

为了对抗这种趋势,我更喜欢 C 而不是 C++,因为它迫使你思考你的代码,以及它如何与硬件更紧密地交互——无情地紧密。

根据长期的经验,我相信 C 迫使你想出更好的问题解决方案,部分原因是让你摆脱困境,而不是强迫你浪费大量时间来满足某些编译器作者认为是个好主意的约束,或者弄清楚“幕后”发生了什么。

在这种情况下,像 C 这样的低级语言会让您花费大量时间专注于硬件并构建良好的数据结构/算法包,而高级语言则让您花费大量时间挠头想知道里面发生了什么,以及为什么你不能在你的特定背景和环境中做一些完全合理的事情。打败你的编译器(强类型是最严重的罪犯)并不是对时间的有效利用。

我可能很适合程序员的模式——我喜欢控制。在我看来,这并不是程序员的性格缺陷。控制是我们获得报酬要做的事情。更具体地说,是完美的控制。C 比 C++ 给你更多的控制权。

就我个人而言,对于 4kb 内存,我认为您不会从 C++ 中获得更多收益,因此只需选择一个似乎最适合这项工作的编译器/运行时组合,因为语言可能并不那么重要。

请注意,无论如何,这也不仅仅与语言有关,因为图书馆也很重要。通常,C 库的最小大小会稍小一些,但我可以想象,针对嵌入式开发的 C++ 库会被缩减,因此请务必进行测试。

有人说 C 编译器可以生成更高效的代码,因为它们不必支持高级 C++ 功能,因此可以更积极地进行优化。

当然,在这种情况下,您可能想要测试两个特定的编译器。

C 在可移植性上获胜——因为它在语言规范上不那么模糊;因此,在不同的编译器等之间提供更好的可移植性和灵活性(更少的麻烦)。

如果您不打算利用 C++ 功能来满足需求,那么就选择 C。

您是否看到为有限的硬件(4KB RAM)开发时坚持使用C89的理由吗?

就我个人而言,当谈到嵌入式应用程序时(当我说嵌入式时,我并不是指winCE、iPhone等。今天臃肿的嵌入式设备)。我的意思是资源有限的设备。我更喜欢 C,尽管我也经常使用 C++。

例如,您正在谈论的设备有 4kb RAM,正是因为这个原因我不会考虑 C++。当然,您也许可以使用 C++ 设计一些小东西,并像其他帖子所建议的那样限制在应用程序中使用它,但 C++“可能”最终会在幕后使您的应用程序变得复杂/膨胀。

你要静态链接吗?您可能想比较使用 C++ 和 C 的静态虚拟应用程序。这可能会导致您考虑使用 C。另一方面,如果您能够在内存需求范围内构建 C++ 应用程序,那就去做吧。

总的来说,恕我直言,在嵌入式应用程序中,我喜欢了解正在发生的一切。谁在使用内存/系统资源、使用量以及原因?他们什么时候释放他们?

当为具有 X 资源量(CPU、内存等)的目标进行开发时。我尝试保持使用这些资源的较低水平,因为您永远不知道未来会出现什么需求,因此您需要向项目添加更多代码,该项目“应该”是一个简单的小型应用程序,但最终会变得更大。

我的选择通常是由我们决定使用的C库决定的,这是根据设备需要做什么来选择的。所以,9/10 次..它最终是 uclibc 或 newlib 和 C。我们使用的内核对此也有很大的影响,或者如果我们正在编写自己的内核。

这也是一种共同点的选择。大多数优秀的 C 程序员在使用 C++ 时都没有问题(尽管许多人在使用它的整个过程中都在抱怨)。但我还没有发现相反的情况是正确的(根据我的经验)。

在我们正在进行的一个项目(涉及底层内核)中,大多数事情都是用 C 完成的,但是一个小型网络堆栈是用 C++ 实现的,因为使用 C++ 实现网络更容易且问题更少。

最终结果是,该设备要么可以工作并通过验收测试,要么不能。如果您可以使用 z 语言在 xx 堆栈和 yy 堆约束中实现 foo,那就去做吧,使用任何能让您提高工作效率的东西。

我个人更喜欢 C,因为:

  • 我知道每一行代码在做什么(以及成本)
  • 我不太了解 C++,无法知道每一行代码在做什么(以及成本)

是的,我对 C++ 很熟悉,但我对它的了解不如对标准 C 的了解。

现在,如果你可以说相反的话,那么,使用你所知道的:)如果它有效,通过测试等..有什么问题?

你有多少 ROM/FLASH?

4kB 的 RAM 仍然意味着有数百 KB 的闪存来存储实际代码和静态数据。这种大小的 RAM 往往只适用于变量,如果您小心使用这些变量,您可以将相当大的程序(就代码行而言)放入内存中。

然而,由于对象的运行时构造规则,C++ 往往会使将代码和数据放入 FLASH 中变得更加困难。在 C 语言中,常量结构可以轻松放入闪存并作为硬件常量对象进行访问。在C++中,常量对象需要编译器在编译时评估构造函数,我认为这仍然超出了C++编译器的能力范围(理论上,你可以做到,但实际上很难做到) 。

因此,在“小 RAM”、“大闪存”类型的环境中,我随时都会选择 C。请注意,C99 是一个不错的中间选择,它具有适用于非基于类的代码的大部分良好的 C++ 功能。

一般来说没有。C++是C的超集。对于新项目尤其如此。

您在避免使用 C++ 构造方面是正确的,因为 C++ 构造在 CPU 时间和内存占用方面可能会很昂贵。

请注意,像多态性这样的东西可能非常有价值——它们本质上是函数指针。如果您发现需要它们,请明智地使用它们。

此外,良好的(精心设计的)异常处理可以使您的嵌入式应用程序比使用传统错误代码处理事务的应用程序更可靠。

恕我直言,选择 C ​​的唯一原因是您平台的 C++ 编译器状态不佳(有错误、优化不佳等)。

这本书 游戏程序员的 C++ 包含有关何时根据 C++ 功能增加代码大小的信息。

你在 C99 中有内联。也许你喜欢演员,但找到合适的演员可能会很麻烦。如果不使用 C 的唯一原因是命名空间,那么我真的会坚持使用 C89。这是因为您可能希望将其移植到稍微不同的嵌入式平台。您稍后可以开始用 C++ 编写相同的代码。但请注意以下内容,其中 C++ 不是 C 的超集。我知道你说你有一个 C89 编译器,但无论如何,这个 C++ 与 C99 进行比较,因为第一项对于自 K&R 以来的任何 C 都是正确的。

'a' 的大小 > 1 在 C 中,而不是在 C++ 中。在 C 中,有 VLA 可变长度数组。例子: func(int i){int a[i]。在 C 中,您有 VAM 变量数组成员。例子: 结构体{int b;int m[];}.

只是想说,不存在拥有“无限”资源的系统。这个世界上的一切都是有限的,每个应用程序都应该考虑资源使用,无论是 ASM、C、JAVA 还是 JavaScript。“只是为了确定”而分配几 Mb 的虚拟设备使得 iPhone 7、Pixel 和其他设备变得极其笨重。无论您拥有 4kb 还是 40 GB。

但从另一个角度来看,反对资源浪费是需要时间来节省这些资源的。如果需要额外花费 1 周的时间用 C 编写一个简单的东西来节省一些时间和一些字节,而不是使用已经实现、测试和分发的 C++。何必呢?就像买一个 USB 集线器一样。是的,你可以自己做,但是会更好吗?更可靠?如果你算一下时间的话会更便宜吗?

只是一个侧面的想法 - 即使你的插座的电源也不是无限的。尝试研究它的来源,你会发现它主要来自燃烧的东西。能量和物质定律仍然有效:物质或能量不会出现或消失,而是会发生变化。

对于内存分配问题,我建议使用 Quantum Platform 及其状态机方法,因为它会在初始化时分配您需要的所有内容。它还有助于缓解争用问题。

该产品可在 C 和 C++ 上运行。

这取决于编译器。

并非所有嵌入式编译器都实现了所有 C++,即使实现了,它们也可能无法很好地避免代码膨胀(这始终是模板的风险)。用一些较小的程序对其进行测试,看看是否遇到任何问题。

但给定一个 好的 编译器,不,没有理由不使用C++。

我刚刚找到了一个如何使用 ISO C++ 进行嵌入式开发的示例,这对于那些在使用 C++ 还是 C 时做出决定的人来说可能很有趣。

它由 Bjarne Stroustrup 提供 在他的主页上:

要了解如何使用 ISO C++ 进行严格的嵌入式系统编程,请参阅 JSF 飞行器 C++ 编码标准.

针对问题的不同方面发布了不同的答案:

“malloc”

之前的一些回复已经谈到了很多这一点。你为什么认为这个电话存在?对于真正的小型平台,malloc 往往不可用,或者绝对是可选的。当您的系统底层有一个 RTOS 时,实现动态内存分配往往是有意义的——但在那之前,它纯粹是危险的。

没有它你也能走得很远。想想所有旧的 FORTRAN 程序,它们甚至没有适当的局部变量堆栈......

全球有许多不同的控制器制造商,当您查看他们的设计和配置所需的指令集时,您可能会遇到很多麻烦。汇编语言的主要缺点是依赖于机器/体系结构。对于开发人员来说,牢记所有为不同控制器完成编码的指令确实是一个巨大的要求。这就是为什么 C 在嵌入式开发中变得更加流行,因为 C 的级别足够高,可以从依赖于硬件的细节中抽象出算法和数据结构,使源代码可以跨各种目标硬件、架构独立的语言移植,并且非常容易转换和维护代码。但我们确实看到了一些高级语言(面向对象),如 C、C++、Python、Java 等。正在不断发展,使它们受到嵌入式系统开发的关注。

在这样一个有限的系统上。只需选择汇编器即可。让您完全控制各个方面,并且不产生任何开销。

可能也会快很多,因为许多嵌入式编译器并不是最好的优化器(特别是如果将其与最先进的编译器(例如我们用于桌面的编译器(intel、Visual Studio 等)进行比较))

“是的,是的……但是 c 是可以重复使用的,而且……”。在这样一个有限的系统上,您很可能不会在不同的系统上重复使用大部分代码。在同一系统上,汇编程序同样可以重复使用。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top