如何克服C ++缺乏对嵌入式系统的工具支持?

时间:2012-08-18 16:47:13

标签: c++ real-time

问题不在于Linux内核。这也不是C与C ++的争论。

我做了一项研究,在我看来,C ++在嵌入式系统的异常处理和内存分配方面缺乏工具支持:

Why is the linux kernel not implemented in C++? 除了接受的答案,另见Ben Collins' answer

Linus Torvalds on C++

  

“[...]任何为C ++设计内核模块的人都是[...]   (b)一个无法看到他正在写的东西的C ++偏执者实际上只是C“

     

“ - 整个C ++异常处理事情从根本上被破坏了。内核的尤其被破坏了。
   - 任何喜欢隐藏内存分配等内容的编译器或语言都不是内核的好选择。“

JOINT STRIKE FIGHTER AIR VEHICLE C++ CODING STANDARDS

  

“不得使用AV规则208 C ++例外”


  1. 异常处理和内存分配是否是C ++显然缺乏工具支持的唯一要点(在此上下文中)?

  2. 要修复异常处理问题,必须提供时间限制,直到异常被抛出后才被捕获?

  3. 你能解释一下为什么内存分配是个问题吗?如何克服这个问题,必须做些什么?

  4. 正如我所看到的那样,在这两种情况下,必须在编译时提供上限 在发生的非常重要的事情上并依赖于运行时的事物。


    答案:

    1. 不,动态广告也是一个问题,但it has been solved

    2. 基本上yes。处理异常所需的时间必须通过分析所有抛出路径来限制。

    3. Embedded systems programming中查看幻灯片中的解决方案“如何在没有新内容的情况下生活”。简而言之:预分配(全局对象,堆栈,池)。

3 个答案:

答案 0 :(得分:14)

嗯,有几件事情。首先,您必须记住STL完全基于OS例程,C标准库和动态分配。在编写内核时,没有为您分配动态内存(您提供它)没有C标准库(您必须在内核上提供一个内置的),并且您提供系统调用。然后有一个事实是C很好地与汇编很好地交互,而C ++很难与汇编接口,因为ABI不一定是常数,也不是名称。由于名称损坏,您将获得全新的并发症。

然后,您必须记住,在构建操作系统时,您需要了解并控制内核使用的内存的各个方面。在C ++中,有很多隐藏的结构你无法控制(vtable,RTTI,异常)会严重干扰你的工作。

换句话说,Linus所说的是,使用C语言,您可以轻松了解正在生成的程序集,并且可以直接在计算机上运行。虽然C ++可以,但你总是需要设置相当多的上下文,并且仍然需要一些C来连接程序集和C.另一个原因是在系统编程中,你需要确切地知道如何调用方法。 C有很好的文档C调用约定,但在C ++中你有this来处理,命名修改等等。

简而言之,这是因为C ++在没有你问的情况下做事情。

Per @Josh的评论如下:C ++在你背后做的另一件事是构造函数和析构函数。它们增加了进入和退出堆栈帧的开销,最重要的是,使组装互操作更加困难,因为当您销毁C ++堆栈帧时,您必须调用其中每个对象的析构函数。这很快变得丑陋。

答案 1 :(得分:9)

为什么某些内核会拒绝代码库中的C ++代码?政治和偏好,但我离题了。

现代操作系统内核的某些部分是用C ++的某些子集编写的。在这些子集中,主要禁用异常和RTTI(有时也不允许多重继承和模板)。

在C中也是如此。某些功能不应在内核环境中使用(例如,VLA)。

在异常和RTTI之外,当我们讨论内核代码(或嵌入代码)时,C ++中的某些功能受到严厉批评。 这些是vtable和构造函数/析构函数。他们带来了一些代码,这似乎被认为是“糟糕的”。如果您不想要构造函数,则不要实现构造函数。如果您担心使用带有构造函数的类,那么也要担心必须使用的函数来初始化结构。 C ++的优点是,你不能忘记在忘记释放内存之外使用dtor。

但是vtable呢?

当您实现包含扩展点的对象(例如Linux文件系统驱动程序)时,您可以使用虚方法实现类似于类的操作。有一个vtable,为什么它太糟糕了?当您对vtable所在的页面有特定要求时,您必须控制此vtable的位置。据我所知,这与linux无关,但在windows下,代码页可以被分页,当你从一个过高的irql调用一个分页函数时,你会崩溃。但是当你使用高级irql时,无论它是什么功能,你都必须注意你调用的函数。如果您在此上下文中不使用虚拟调用,则无需担心。在嵌入式软件中,这可能会更糟糕,因为(很少)您需要直接控制代码所在的代码页,但即使在那里,您也可以影响链接器的功能。

那么为什么这么多人如此坚持'在内核中使用C'?

因为他们要么被工具链问题烧毁,要么被过度热心的开发人员用内核模式中的最新东西烧掉。

也许内核模式开发人员相当保守,而C ++是一个太新奇的东西......


为什么在内核模式代码中没有使用异常?

因为它们需要为每个函数生成一些代码,所以在代码路径中引入复杂性而不处理异常对于内核模式组件是不利的,因为它会杀死系统。

在C ++中,当抛出异常时,必须展开堆栈并且必须调用相应的析构函数。这至少涉及一些开销。这几乎可以忽略不计,但它会带来成本,这可能不是你想要的。 (注意我不知道桌面基础展开的实际成本是多少,我想我读到没有运行异常时没有成本,但是......我想我必须查找它。)

一个不能抛出异常的代码路径可以更容易理解,然后就可以了。 所以:

int f( int a )
{
   if( a == 0 )
      return -1;

   if( g() < 0 )
      return -2;
   f3(); 

   return h();
}

我们可以在这个函数中推理每个出口路径,因为我们可以很容易地看到所有返回,但是当启用异常时,函数可能会抛出,我们无法保证函数所采用的实际路径是什么。这是代码可以做一些我们无法立即看到的事情的确切点。 (当启用异常时,这是错误的C ++代码。)

第三点是,您希望用户模式应用程序崩溃,当出现意外情况时(例如,当内存耗尽时),用户模式应用程序应该崩溃(在释放资源之后)以允许开发人员调试问题或者至少得到一个很好的错误消息。你不应该在内核模式模块中有一个未被捕获的异常。

请注意,所有这些都可以克服,Windows内核中存在SEH异常,因此第2 + 3点在NT内核中并不是非常好的点。


内核中没有C ++的内存管理问题。例如。 NT内核头文件为new和delete提供了重载,允许您指定分配的池类型,但与用户模式应用程序中的new和delete完全相同。

答案 2 :(得分:3)

我真的不喜欢语言战争,并投票决定再次关闭。但无论如何......

  

嗯,有几件事情。首先,您必须记住STL完全基于OS例程,C标准库和动态分配。当您编写内核时,没有动态内存分配给您(您提供它)没有C标准库(您必须提供一个内置于内核之上),并且您正在提供系统调用。然后有一个事实是C很好地与汇编很好地交互,而C ++很难与汇编接口,因为ABI不一定是常数,也不是名称。由于名称损坏,你会得到一个全新的复杂程度。

不,使用C ++,您可以声明具有extern "C"(或可选extern "assembly")调用约定的函数。这使得名称与同一平台上的其他所有内容兼容。

  

然后,您必须记住,在构建操作系统时,您需要了解并控制内核使用的内存的各个方面。在C ++中,有很多隐藏的结构你无法控制(vtable,RTTI,异常)会严重干扰你的工作。

编写内核功能时必须小心,但这并不仅限于C ++。当然,您不能使用std::vector<byte>作为内存分配的基础,但您也不能使用malloc。您没有拥有来为所有C ++类使用虚函数,多继承和动态分配,是吗?

  

换句话说,Linus所说的是,使用C语言,您可以轻松了解正在生成的程序集,并且可以直接在计算机上运行。虽然C ++可以,但你总是需要设置相当多的上下文,并且仍然需要一些C来连接程序集和C.另一个原因是在系统编程中,你需要确切地知道如何调用方法。 C有很好的文档C调用约定,但是在C ++中你可以用它来处理,命名修改等等。

Linus可能声称他可以发现对f(x)的每次通话,并立即看到它正在调用g(x)h(x)q(x) 20级。仍然MyClass M(x);是一个很大的谜,因为它可能会在他的背后调用一些未知的代码。在那里迷失了我。

  

简而言之,这是因为C ++在没有你问的情况下做事情。

如何?如果我为类编写构造函数和析构函数,那是因为我要求执行代码。不要告诉我C可以在没有执行某些代码的情况下神奇地复制一个对象

  

Per @Josh的评论如下:C ++在你背后做的另一件事是构造函数和析构函数。它们增加了进入和退出堆栈帧的开销,最重要的是,使组装互操作更加困难,因为当您销毁C ++堆栈帧时,您必须调用其中每个对象的析构函数。这很快变得丑陋。

构造函数和析构函数不会在背后添加代码,只有在需要时它们才会存在。仅在需要时才调用析构函数,例如需要释放动态内存时。不要告诉我C代码没有这个。


在Linux和Windows中缺乏C ++支持的一个原因是很多从事内核工作的人在C ++可用之前很久就已经这样做了。我见过来自Windows内核开发人员的帖子,认为不需要C ++支持,因为非常很少用C ++编写的设备驱动程序。捉住22!


  

异常处理和内存分配是否是C ++显然缺乏工具支持的唯一点(在此上下文中)?

如果处理不当,请不要使用它。您不必使用多重继承,动态分配和在各处抛出异常。如果返回错误代码有效,那很好。那样做!

  

要解决异常处理问题,必须提供时间限制,直到异常被抛出后才被捕获?

不,但您只是无法使用内核中的应用程序级别功能。使用std::vector<byte>实现动态内存不是一个好主意,但是谁会真的尝试呢?

  

你能解释一下为什么内存分配是一个问题吗?如何克服这个问题,必须做些什么?

根据功能实现下面的层上的内存分配使用标准库功能,内存管理将是一个问题。使用malloc调用实现malloc同样愚蠢。但谁会尝试呢?