C++ 在嵌入式系统中的使用

发布于 2024-07-05 03:08:27 字数 173 浏览 5 评论 0 原文

在嵌入式系统中应避免使用 C++ 的哪些功能?

请按以下原因对答案进行分类:

  • 内存使用
  • 代码大小
  • 速度
  • 可移植性

编辑:让我们使用具有 64k RAM 的 ARM7TDMI 作为目标来控制答案的范围。

What features of C++ should be avoided in embedded systems?

Please classify the answer by reason such as:

  • memory usage
  • code size
  • speed
  • portability

EDIT: Lets' use an ARM7TDMI with 64k ram as a target to control the scope of the answers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

你げ笑在眉眼 2024-07-12 03:08:27

ATMega GCC 3.something 的一个特殊问题让我感到惊讶:当我向我的一个类添加虚拟 ember 函数时,我必须添加一个虚拟析构函数。 此时,链接器请求运算符delete(void *)。 我不知道为什么会发生这种情况,并为该运算符添加一个空定义解决了问题。

One particular problem that surprised me with ATMega GCC 3.something: when I added a virtual ember function to one of my classes, I had to add a virtual destructor. At that point, the linker asked for operator delete(void *). I have no idea why that happens, and adding an empty definition for that operator slolved the problem.

触ぅ动初心 2024-07-12 03:08:27

请注意,异常的成本取决于您的代码。 在我分析的一个应用程序(ARM968 上的一个相对较小的应用程序)中,异常支持使执行时间增加了 2%,并且代码大小增加了 9.5 KB。 在此应用程序中,只有在发生严重错误的情况下才会引发异常(即从未在实践中发生),这使得执行时间开销非常低。

Note that the cost of exceptions depends on your code. In one application I profiled (a relatively small one on ARM968), exception support added 2 % to execution time, and code size was increased by 9.5 KB. In this application, exceptions were thrown only in case something seriously bad happened -- i.e. never in practice -- which kept the execution time overhead very low.

度的依靠╰つ 2024-07-12 03:08:27

使用 ARM7 并假设您没有外部 MMU,动态内存分配问题可能更难调试。 我将“明智地使用 new/delete/free/malloc”添加到指南列表中。

Using an ARM7 and assuming you don't have an external MMU, dynamic memory allocation problems can be harder to debug. I'd add "judicious use of new / delete / free / malloc" to the list of guidelines.

爱殇璃 2024-07-12 03:08:27

如果您使用的是 ARM7TDMI,请避免未对齐的内存访问所有费用。

基本的 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);
  • 在 x86/x64 CPU 上,将打印“7TDM”。
  • 在 SPARC CPU 上,这会转储带有总线错误的核心。
  • 在 ARM7TDMI CPU 上,这可能会打印类似“7ARM”或“ITDM”的内容,假设变量“x”在 32 位边界上对齐(这取决于“x”所在的位置以及正在使用的编译器选项)等)并且您正在使用小端模式。 这是未定义的行为,但几乎可以保证它不会按您想要的方式工作。

If you're using an ARM7TDMI, avoid unaligned memory accesses at all costs.

The basic ARM7TDMI core does not have alignment checking, and will return rotated data when you do an unaligned read. Some implementations have additional circuitry for raising an ABORT exception, but if you don't have one of those implementations, finding bugs due to unaligned accesses is very painful.

Example:

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);
  • On an x86/x64 CPU, this prints "7TDM".
  • On a SPARC CPU, this dumps core with a bus error.
  • On an ARM7TDMI CPU, this might print something like "7ARM" or "ITDM", assuming that the variable "x" is aligned on a 32-bit boundary (which depends on where "x" is located and what compiler options are in use, etc.) and you are using little-endian mode. It's undefined behavior, but it's pretty much guaranteed not to work the way you want.
挽袖吟 2024-07-12 03:08:27

在大多数系统中,您不想使用new / delete,除非您使用从自己的托管堆中提取的自己的实现覆盖了它们。 是的,它会起作用,但你正在处理一个内存受限的系统。

In most systems you do not want to use new / delete unless you have overridden them with your own implementation that pulls from your own managed heap. Yes, it'll be work but you are dealing with a memory constrained system.

送君千里 2024-07-12 03:08:27

我不会说这有一个硬性规定; 这在很大程度上取决于您的应用程序。 嵌入式系统通常是:

  • 可用内存量受到更多限制
  • 通常在较慢的硬件上运行
  • 往往更接近硬件,即以某种方式驱动它,例如摆弄寄存器设置。

就像任何其他开发一样,您应该平衡您提到的所有要点与您给出/派生的要求。

I wouldn't have said there's a hard and fast rule to this; it depends a lot on your application. Embedded systems are typically:

  • More constrained in the amount of memory they have available
  • Often run on slower hardware
  • Tend to be closer to hardware i.e. driving it in some way like fiddling with register settings.

Just like any other development though, you should balance all of the points you've mentioned against the requirements you were given / derived.

慕巷 2024-07-12 03:08:27

关于代码膨胀,我认为罪魁祸首更有可能是内联而不是模板。

例如:

// 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”,那么您最终将得到“foo”的 3 个不同副本'。 看不到模板!

编辑:来自 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”的代码。

Regarding code bloat, I think the culprit is much more likely to be inline than templates.

For example:

// 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> (); }

The linker most likely will merge all the definitions of 'foo' into a single translation unit. Therefore the size of 'foo' is no different to that of any other namespace function.

If your linker doesn't do this, then you can use an explicit instantiation to do that for you:

// 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> (); }

Now consider the following:

// 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 (); }

If the compiler decides to inline 'foo' for you then you will end up with 3 different copies of 'foo'. No templates in sight!

EDIT: From a comment above from InSciTek Jeff

Using explicit instantiations for the functions that you know will be used only, you can also ensure that all unused functions are removed (which may actually reduce the code size compared with the non template case):

// 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 ();

Unless your tool chain is completely broken, the above will generate code only for 'f1' and 'f2'.

浮生未歇 2024-07-12 03:08:27

时间函数通常依赖于操作系统(除非您重写它们)。 使用您自己的函数(特别是如果您有 RTC)

只要您有足够的代码空间就可以使用模板 - 否则不要使用它们

异常不是很可移植还有

printf 函数

如果速度是问题,也许你应该使用自己的容器而不是 STL(例如 std::map 容器以确保键相等,与 'less' 进行 2 (yes 2) 比较运算符( a [小于] b == false && b [小于] a == false 意味着 'less' 是 std::map 类接收的唯一比较参数(而不是)。这可能会导致关键例程中的一些性能损失,

异常会增加代码大小(您可以确定,有时甚至

可能需要重写 较大的代码时性能也会受到影响)。还因为它们在很多方面都依赖于操作系统(特别是在处理线程安全内存分配时)

在“未知”环境中不是线程安全的。

malloc 使用 _end 变量(通常在链接器脚本中声明)来分配内存,但这有时 应使用 Thumb 而不是 Arm 模式。 它可以提高性能。

因此,对于 64k 内存,我认为 C++ 及其一些出色的功能(STL、异常等)可能有点过分了。 我肯定会选择C。

time functions are usually OS dependent (unless you rewrite them). Use your own functions (especially if you have a RTC)

templates are ok to use as long as you have enough space for code - othwerise don't use them

exceptions are not very portable also

printf functions that don't write to a buffer are not portable (you need to be somehow connected to the filesystem to write to a FILE* with printf). Use only sprintf, snprintf and str* functions (strcat, strlen) and of course their wide char corespondents (wcslen...).

If speed is the problem maybe you should use your own containers rather than STL (for example the std::map container to make sure a key is equal does 2 (yes 2) comparisons with the 'less' operator ( a [less than] b == false && b [less than] a == false mean a == b ). 'less' is the only comparison parameter received by the std::map class (and not only). This can lead to some performance loss in critical routines.

templates, exceptions are increasing the code size (you can be sure of this). sometimes even performance is affected when having a larger code.

memory allocation functions probably need to be rewritten also because they are OS dependent in many ways (especially when dealing with thread safety memory allocation).

malloc uses the _end variable (declared usually in the linker script) to allocate memory but this is not thread safe in "unknown" environments.

sometimes you should use Thumb rather than Arm mode. It can improve performance.

So for 64k memory I would say that C++ with some of its nice features (STL, exceptions etc) can be overkill. I would definitely choose C.

猫卆 2024-07-12 03:08:27

使用 GCC ARM 编译器和 ARM 自己的 SDT 后,我有以下评论:

  • ARM SDT 生成更紧凑、更快
    代码,但非常昂贵(> Eur5k
    每个座位!)。 在我之前的工作中,我们
    使用这个编译器,没问题。

  • GCC ARM 工具运行良好
    不过这是我自己用的
    项目 (GBA/DS)。

  • 使用“拇指”模式,因为这会减少
    代码大小显着。 在 ARM 的 16 位总线变体(例如 GBA)上也有速度优势。

  • 64k 对于 C++ 来说太小了
    发展。 我会使用 C & 汇编器
    在那种环境下。

在这么小的平台上,您必须小心堆栈的使用。 避免递归、大型自动(本地)数据结构等。堆的使用也将是一个问题(new、malloc 等)。 C 将使您能够更好地控制这些问题。

Having used both the GCC ARM compiler and the ARM's own SDT I'd have the following comments:

  • The ARM SDT produces tighter, faster
    code but is very expensive (>Eur5k
    per seat!). At my previous job we
    used this compiler and it was ok.

  • The GCC ARM tools works very well
    though and it's what I use on my own
    projects (GBA/DS).

  • Use 'thumb' mode as this reduces
    code size significantly. On 16 bit bus variants of the ARM (such as the GBA) there is also a speed advantage.

  • 64k is seriously small for C++
    development. I'd use C & Assembler
    in that environment.

On such a small platform you'll have to be careful of stack usage. Avoid recursion, large automatic (local) data structures etc. Heap usage will also be an issue (new, malloc etc). C will give you more control of these issues.

白鸥掠海 2024-07-12 03:08:27

如果您正在使用针对嵌入式开发或特定嵌入式系统的开发环境,那么它应该已经限制了您的一些选项。 根据目标的资源能力,它将关闭上述一些项目(RTTI、例外等)。 这是更简单的方法,而不是记住会增加大小或内存需求的内容(尽管无论如何,您应该在心理上了解这一点)。

If you are using a development environment targeted toward embedded development or a particular embedded system, it should have limited some of the options for you already. Depending on the resource capabilities of your target, it will turn off some of the aforementioned items (RTTI, exceptions, etc.). This is the easier route to go, rather than keeping in mind what will increase size or memory requirements (although, you should get to know that mentally anyway).

我不在是我 2024-07-12 03:08:27

对于嵌入式系统,您主要希望避免具有明确的异常运行时成本的事情。 一些示例:例外和 RTTI (包括 dynamic_casttypeid< /a>)。

For embedded systems, you'll predominately want to avoid things that have a definite abnormal runtime cost. Some examples: exceptions, and RTTI (to include dynamic_cast and typeid).

等往事风中吹 2024-07-12 03:08:27

确保您知道您的嵌入式平台的编译器支持哪些功能,并确保您了解您的平台的特殊性。 例如,TI 的 CodeComposer 编译器不执行自动模板实例化。 结果,如果你想使用STL的排序,你需要手动实例化五个不同的东西。 它也不支持流。

另一个例子是,您可能使用的是 DSP 芯片,该芯片不支持浮点运算的硬件。 这意味着每次使用浮点型或双精度型时,您都会付出函数调用的成本。

总而言之,了解有关嵌入式平台和编译器的所有信息,然后您就会知道要避免哪些功能。

Make sure you know what features are supported by the compiler for your embedded platform and also make sure you know the peculiarities of your platform. For example the TI's CodeComposer compiler does not do automatic template instantiations. As a result, if you want to use STL's sort, you need to instantiate five different things manually. It also does not support streams.

Another example is that you may be using a DSP chip, which does not have hardware support for floating point operations. That means every time you use a float or a double you pay the cost of a function call.

To summarize, know everything there is to know about your embedded platform and your compiler, and then you will know which features to avoid.

醉生梦死 2024-07-12 03:08:27

选择避免某些功能应始终通过对软件在硬件上的行为进行定量分析来驱动,并使用选择的工具链,在您的域所带来的约束。 C++ 开发中有很多“不该做”的传统观念,它们是基于迷信和古代历史而不是硬数据。 不幸的是,这通常会导致编写大量额外的解决方法代码,以避免使用某人在某个地方曾经遇到过问题的功能。

Choosing to avoid certain features should always be driven by quantitative analysis of the behavior of your software, on your hardware, with your chosen toolchain, under the constraints your domain entails. There are a lot of conventional wisdom "don'ts" in C++ development which are based on superstition and ancient history rather than hard data. Unfortunately, this often results in a lot of extra workaround code being written to avoid using features that someone, somewhere, had a problem with once upon a time.

祁梦 2024-07-12 03:08:27

例外可能是要避免的最常见的答案。 大多数实现都有相当大的静态内存成本或运行时内存成本。 它们还往往使实时保证变得更加困难。

请查看此处,了解编写的编码标准的一个很好的示例对于嵌入式 C++。

Exceptions are likely going to be the most common answer of what to avoid. Most implementations have a fairly large static memory cost, or a runtime memory cost. They also tend to make realtime guarantees harder.

Look here for a pretty good example of a coding standard written for embedded c++.

感悟人生的甜 2024-07-12 03:08:27

对于早期嵌入式 C++ 标准

请参阅此 文章

嵌入式 C++ 标准是 C++ 的真子集,即它没有添加任何内容。 删除了以下语言功能:

  • 多重继承
  • 虚拟基类
  • 运行时类型信息 (typeid)
  • 新样式转换(static_cast、dynamic_cast、reinterpret_cast 和
    const_cast)
  • 可变类型限定符
  • 命名空间
  • 异常
  • 模板

wiki 页面 上注意到 Bjarne Stroustrup 说(关于 EC++ 标准),“据我所知,EC++ 已经死了(2004 年),如果不是的话,它也应该死了。” Stroustrup 继续推荐 Prakash 的答案引用的文档

It's an interesting read for the Rationale on the early Embedded C++ standrard

See this article on EC++ as well.

The Embedded C++ std was a proper subset of C++, i.e. it has no additions. The following language features were removed:

  • Multiple inheritance
  • Virtual base classes
  • Run-time type information (typeid)
  • New style casts (static_cast, dynamic_cast, reinterpret_cast and
    const_cast)
  • The mutable type qualifier
  • Namespaces
  • Exceptions
  • Templates

It's noted on the wiki page that Bjarne Stroustrup says (of the EC++ std), "To the best of my knowledge EC++ is dead (2004), and if it isn't it ought to be." Stroustrup goes on to recommend the document referenced by Prakash's answer.

虚拟世界 2024-07-12 03:08:27

RTTI 和异常处理:

  • 增加代码大小
  • 降低性能
  • 通常可以被更便宜的机制或更好的软件设计所取代。

模板:

  • 如果代码大小是一个问题,请小心使用它们。 如果您的目标 CPU 没有或只有一个非常小的指令缓存,它也可能会降低性能。 (如果不小心使用,模板往往会导致代码膨胀)。 Otoh 巧妙的元编程也可以减少代码大小。 对于他的说法,目前还没有明确的答案。

虚拟函数和继承:

  • 这些对我来说很好。 我几乎所有的嵌入式代码都是用 C 语言编写的。这并不妨碍我使用函数指针表来模拟虚拟函数。 它们从未成为性能问题。

RTTI and Exception Handling:

  • Increases code-size
  • Decreases performance
  • Can often be replaced by cheaper mechanisms or a better software-design.

Templates:

  • be careful with them if code-size is an issue. If your target CPU has no or only a very tiny ínstruction cache it may reduce the performance as well. (templates tend to bloat code if used without care). Otoh clever meta-programming can decrease the code-size as well. There is no clear cut answer on his.

Virtual functions and inheritance:

  • These are fine for me. I write almost all of my embedded code in C. That does not stop me from using function-pointer tables to mimic virtual functions. They never became a peformance problem.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文