嵌入式系统应该避免使用C ++的哪些功能?
请按以下原因对答案进行分类:
编辑:让我们使用64k ram的ARM7TDMI作为目标来控制答案的范围。
答案 0 :(得分:18)
RTTI和异常处理:
<强>模板:强>
虚拟功能和继承:
答案 1 :(得分:13)
选择避免某些功能应始终通过定量分析 软件的行为, 硬件,选择来驱动工具链,在你的域的约束条件下。在C ++开发中有很多传统的智慧“不应该”,它们基于迷信和古代历史,而不是硬数据。不幸的是,这通常会导致编写大量额外的解决方法代码,以避免使用某个某个地方曾经有问题的功能。
答案 2 :(得分:10)
例外情况可能是避免什么的最常见答案。大多数实现具有相当大的静态内存成本或运行时内存成本。他们也倾向于使实时保证更难。
查看here,了解为嵌入式c ++编写的编码标准的一个很好的例子。
答案 3 :(得分:5)
文档“Information technology — Programming languages, their environments and system software interfaces — Technical Report on C++ Performance”还提供了有关嵌入式设备的C ++编程的一些很好的信息。
答案 4 :(得分:4)
这是早期Rationale
上Embedded C++ standrard的有趣读物在EC ++上也可以看到article。
嵌入式C ++标准版是C ++的一个合适子集,即没有添加内容。删除了以下语言功能:
在wiki page上注意到Bjarne Stroustrup所说的(EC ++标准),“据我所知,EC ++已经死了(2004年),如果它不应该是它。” Stroustrup继续推荐Prakash回答引用的document。
答案 5 :(得分:3)
使用ARM7并假设您没有外部MMU,动态内存分配问题可能难以调试。我将“明智地使用new / delete / free / malloc”添加到指南列表中。
答案 6 :(得分:3)
如果您使用的是ARM7TDMI,不惜一切代价避免unaligned memory accesses 。
基本的ARM7TDMI内核没有对齐检查,并且在执行未对齐读取时将返回旋转的数据。某些实现具有用于引发ABORT
异常的附加电路,但是如果您没有其中一个实现,则由于未对齐访问而发现错误是非常痛苦的。
示例:
const char x[] = "ARM7TDMI";
unsigned int y = *reinterpret_cast<const unsigned int*>(&x[3]);
printf("%c%c%c%c\n", y, y>>8, y>>16, y>>24);
答案 7 :(得分:2)
在大多数系统中,您不希望使用 new / delete ,除非您使用自己的托管堆中的实现覆盖它们。是的,它会工作,但你正在处理一个内存受限的系统。
答案 8 :(得分:1)
关于代码膨胀,我认为罪魁祸首更可能是内联而不是模板。
例如:
// foo.h
template <typename T> void foo () { /* some relatively large definition */ }
// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }
// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }
// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }
链接器很可能会将'foo'的所有定义合并到一个翻译单元中。因此,'foo'的大小与任何其他命名空间函数的大小没有区别。
如果您的链接器没有这样做,那么您可以使用显式实例化为您执行此操作:
// foo.h
template <typename T> void foo ();
// foo.cc
#include "foo.h"
template <typename T> void foo () { /* some relatively large definition */ }
template void foo<int> (); // Definition of 'foo<int>' only in this TU
// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }
// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }
// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }
现在考虑以下事项:
// foo.h
inline void foo () { /* some relatively large definition */ }
// b1.cc
#include "foo.h"
void b1 () { foo (); }
// b2.cc
#include "foo.h"
void b2 () { foo (); }
// b3.cc
#include "foo.h"
void b3 () { foo (); }
如果编译器决定为你内联'foo',那么你将得到3个不同的'foo'副本。看不到任何模板!
编辑:来自InSciTek Jeff上面的评论
对于您只知道将使用的函数使用显式实例化,您还可以确保删除所有未使用的函数(与非模板情况相比,这实际上可能减少代码大小):
// a.h
template <typename T>
class A
{
public:
void f1(); // will be called
void f2(); // will be called
void f3(); // is never called
}
// a.cc
#include "a.h"
template <typename T>
void A<T>::f1 () { /* ... */ }
template <typename T>
void A<T>::f2 () { /* ... */ }
template <typename T>
void A<T>::f3 () { /* ... */ }
template void A<int>::f1 ();
template void A<int>::f2 ();
除非你的工具链完全被破坏,否则上面只会为'f1'和'f2'生成代码。
答案 9 :(得分:1)
时间函数通常取决于操作系统(除非你重写它们)。使用您自己的功能(特别是如果您有RTC)
只要你有足够的代码空间,就可以使用模板 - 不要使用它们
异常也不是非常便携
不写入缓冲区的printf函数不可移植(您需要以某种方式连接到文件系统以使用printf写入FILE *)。仅使用sprintf,snprintf和str *函数(strcat,strlen),当然还有广泛的char corespondents(wcslen ...)。如果速度是问题,也许您应该使用自己的容器而不是STL(例如std :: map容器以确保密钥相等 2(是2)与'less'运算符(a [小于] b == false&amp;&amp; b [小于] a == false表示a == b)。'less'是std :: map类接收的唯一比较参数(而且不仅仅是)。这可能会导致关键例程中的性能损失。
模板,例外是增加代码大小(你可以肯定这一点)。有时甚至性能会因代码更大而受到影响。
内存分配函数可能也需要重写,因为它们在很多方面都依赖于操作系统(特别是在处理线程安全内存分配时)。
malloc使用_end变量(通常在链接描述文件中声明)来分配内存,但这在“未知”环境中不是线程安全的。
有时你应该使用Thumb而不是Arm模式。它可以提高性能。
因此,对于64k内存,我会说C ++具有一些很好的功能(STL,异常等)可能有点过分。我肯定会选择C.
答案 10 :(得分:1)
我不会说这对此有一个硬性规定;这在很大程度上取决于你的应用。嵌入式系统通常是:
就像其他任何发展一样,你应该平衡你提到的所有要点与你给出/得出的要求。
答案 11 :(得分:1)
使用了GCC ARM编译器和ARM自己的SDT后,我有以下评论:
ARM SDT产生更紧,更快 代码但非常昂贵(&gt; Eur5k 每个座位!)。在我以前的工作我们 使用这个编译器就可以了。
GCC ARM工具运行良好 虽然这是我自己使用的 项目(GBA / DS)。
使用“拇指”模式,因为这会减少 代码大小显着。在ARM的16位总线变体(例如GBA)上,还有速度优势。
对于C ++来说,64k非常小 发展。我会用C&amp;汇编 在那种环境中。
在这么小的平台上,你必须要小心堆栈的使用。避免递归,大型自动(本地)数据结构等。堆使用也是一个问题(new,malloc等)。 C将使您更好地控制这些问题。
答案 12 :(得分:0)
确保您知道嵌入式平台的编译器支持哪些功能,并确保您了解平台的特性。例如,TI的CodeComposer编译器不执行自动模板实例化。因此,如果要使用STL的排序,则需要手动实例化五个不同的事物。它也不支持流。
另一个例子是你可能正在使用DSP芯片,它没有浮点运算的硬件支持。这意味着每次使用浮点数或双精度数都需要支付函数调用的成本。
总而言之,了解有关嵌入式平台和编译器的所有信息,然后您将了解要避免的功能。
答案 13 :(得分:0)
对于嵌入式系统,您主要希望避免运行成本明显异常的事情。一些示例:例外和RTTI(包括dynamic_cast和typeid)。
答案 14 :(得分:0)
使用ATMega GCC感到惊讶的一个特殊问题3.something:当我向其中一个类添加虚拟余烬函数时,我不得不添加一个虚拟析构函数。此时,链接器要求删除操作符(void *)。我不知道为什么会发生这种情况,并为该运算符添加一个空的定义解决了这个问题。
答案 15 :(得分:0)
如果您使用的是针对嵌入式开发或特定嵌入式系统的开发环境,那么它应该已经为您限制了一些选项。根据目标的资源功能,它将关闭一些上述项目(RTTI,例外等)。这是更容易的路线,而不是记住会增加大小或内存需求的内容(尽管如此,你应该在心理上了解这一点)。
答案 16 :(得分:0)
请注意,例外费用取决于您的代码。在我分析的一个应用程序(ARM968上相对较小的应用程序)中,异常支持增加了2%的执行时间,代码大小增加了9.5 KB。在这个应用程序中,仅在出现严重错误的情况下抛出异常 - 即从未在实践中 - 这使得执行时间开销非常低。