7

我正在为我的大学做一个小组高级项目,我在尝试让我的代码工作时遇到了一个主要障碍。

我们的 8 位 Atmel 微控制器的编译器不支持 new 或 delete 运算符,也不支持 C++ STL。我可以用 C 编写它,但我必须实现一个我以前从未做过的 A* 算法。虽然我最初尝试过 C,但我很快意识到我以前从未使用过纯 C。尝试使用结构和函数对对象进行建模让我放慢了速度,因为我已经习惯了更简洁的 C++ 语法。

无论如何,我的编译器缺点的确切措辞可以在这里找到:http ://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_cplusplus

为了克服它们并仍然使用 C++,我考虑了以下可能性。1)不要分配任何东西,只需使用模板在堆栈上生成固定数组。2)一旦我为它们分配了空间,就分配并找到一些技巧来调用对象的构造函数。放置 new 不是一个选项,因为 new 不是运算符。3) 只需使用 C 并把它吸起来,它是一个微控制器,为什么我会喜欢它?4)找到一个更好的编译器,它可能会花费$$$。

第二种选择是最难的,但就我如何编写这段代码而言,它会带来最大的回报。但是,我想如果我弄错了调试它可能会很痛苦。我正在考虑在堆栈上创建对象,将它们的位复制到分配的空间中,然后将对象中的位清零,这样它就不会调用它的析构函数。为此,我将使用 unsigned char 指针和 sizeof 运算符直接访问这些位以获取字节数。

这听起来很糟糕,我不知道它是否可以可靠地工作,但我正在考虑。我知道 vtables 可能是个问题,但我不打算拥有任何 vtables,因为它只是一个 8 位微控制器。

4

8 回答 8

23

不要与你的工具抗争。如果您的嵌入式系统唯一的编译器是 C 编译器,请学习 C - 这并不难。仅仅为了解决一个相当简单的编程问题而试图制作这两种语言的一些混蛋版本只会以眼泪收场。

换个角度来看,如果您的嵌入式平台甚至不支持 C 编译器,而只支持汇编器,您的第一个冲动会是坐下来用汇编器编写 C++ 编译器吗?我希望不是,我希望你能坐下来学习使用汇编程序来完成你的任务——编写一个 C++ 编译器(甚至是一个 C 编译器)完全不适合你的时间,而且几乎肯定会导致失败。

于 2009-04-19T14:20:51.093 回答
10

只是为了记录,将对象中的位归零不会影响析构函数是否被调用(除非编译器有一个特殊的怪癖来启用这种行为)。只需在你的析构函数中写一些日志语句来测试它。

构建您的程序不分配任何东西可能是系统的设计方式。我以前没有使用过嵌入式系统,但是我读过一些有经验的嵌入式商店,他们不鼓励使用动态内存,因为运行时环境中的动态内存很少。


但是,如果必须,您仍然可以使用放置新。如果您没有<new>标题,以下是我的 GCC 版本上直接来自它的相关行:

// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) throw() { return __p; }
inline void* operator new[](std::size_t, void* __p) throw() { return __p; }

// Default placement versions of operator delete.
inline void  operator delete  (void*, void*) throw() { }
inline void  operator delete[](void*, void*) throw() { }

将其粘贴在每个使用放置新/删除的源文件包含的头文件中。

测试这个的示例文件:

#include <cstdio>
#include <new>

int
main(int argc, char** argv)
{
    typedef char const* cstr;
    char foobar[16];
    cstr* str = new (&foobar) cstr(argc > 1 ? argv[1] : "Hello, world!");
    std::puts(*str);
    str->~cstr();
}

在我的 GCC 版本上,这根本不使用libstdc++(如果-fno-exceptions使用)。


现在,如果你想将它与malloc(如果你的平台提供这个)结合起来,那么你可以这样做:

#include <cstdio>
#include <cstdlib>

inline void* operator new  (std::size_t n) {return std::malloc(n);}
inline void* operator new[](std::size_t n) {return std::malloc(n);}
inline void  operator delete  (void* p) {std::free(p);}
inline void  operator delete[](void* p) {std::free(p);}

int
main(int argc, char** argv)
{
    typedef char const* cstr;
    cstr* str = new cstr(argc > 1 ? argv[1] : "Hello, world!");
    std::puts(*str);
    delete str;
}

这允许您使用您熟悉的标准new/ ,而无需使用.deletelibstdc++

祝你好运!

于 2009-04-19T14:31:10.723 回答
6

我认为你是从一个不太理想的角度来处理这个问题。

您专注于编译器(或缺少编译器),而不是专注于硬件。

您的主要问题最可能的答案是“因为硬件不支持所有 C++ 东西”。嵌入式硬件(微控制器)以定制硬件设计而著称——内存映射、中断处理程序、I/O 等。

在我看来,您应该首先花一些时间阅读有关微控制器的硬件书籍,了解设备的来龙去脉——即它是如何设计的以及主要用途是什么。一些设计用于快速内存操作,一些用于快速 I/O 处理,一些用于 A/D 类型的工作,一些用于信号处理。微控制器的类型决定了他们为它编写的汇编指令,这决定了任何更高级别的编译器可以有效地做什么。

如果这很重要,请花一些时间查看汇编器 - 它会告诉您设计人员认为什么是重要的。它还将告诉您很多关于您可以从高级编译器中获得多少的信息。

通常,微控制器不支持 C++,因为设计真的不关心对象或花哨的内存处理(从 C++ 的角度来看)。可以做到,但是您经常尝试在方孔中敲击圆钉,以使构造函数和析构函数(以及“新”和“删除”)在微环境中工作。

如果你有一个用于这个单元的 C 编译器,那么认为它是一种祝福。一个好的 C 编译器通常“绰绰有余”来创建出色的嵌入式软件。

干杯,

-理查德

于 2009-04-19T15:46:22.773 回答
4

仅仅因为它没有这些工具并不意味着您不能从 C++ 中受益。如果项目足够大,那么仅使用面向对象设计就足够了。

如果它不支持“新”,那么可能是因为自动区分堆和堆栈是没有意义的。这可能是因为您的内存配置。这也可能是因为内存资源非常有限,只有非常小心的分配才有意义。如果您绝对必须实现自己的“新”运算符,您可能会考虑调整Doug Lea 的 malloc。我相信他在类似的情况下开始了他的分配器(重新实现 C++ 的新功能)。

我喜欢 STL,但没有它仍然可以做有用的事情。根据项目的范围,您最好只使用数组。

于 2009-04-19T14:28:03.120 回答
2

我有一个类似的编译器,它实现了一个奇怪的Embedded-C++ 标准版本。我们有operator new哪些会为我们调用构造函数,而在大多数情况下会调用析构函数。编译器/运行时供应商去实施trycatch使用setjmp,并longjmp工程师提供方便。问题是他们从未提到 athrow不会导致调用本地对象的析构函数!

无论如何,在有人编写了一个像标准 C++ 一样的应用程序之后,我们的团队继承了代码库:使用 RAII 技术和所有其他优点。我们最终改用我们许多人所说的面向对象的 C来重写它。您可能想考虑硬着头皮直接用 C 语言编写。而不是构造函数,有一个显式调用的初始化方法。析构函数成为显式调用的终止方法。没有多少 C++ 是您不能很快在 C 中模仿的。是的,MI 很痛苦……但是单继承很容易。查看此 PDF以获得一些想法。它几乎描述了我们采用的方法。我真希望我把我们的方法写在某个地方……

于 2009-04-19T15:09:23.177 回答
1

您可以在我的 A* 教程网站上找到一些有用的代码。尽管我为支持这一点而编写的代码使用了 STL,但应该很容易去除 STL 支持。此外,它还包含一个池分配器 (fsa.h),我编写它以加速游戏机上的 STL。它是 C++ 代码,但我最初是从 C 移植过来的,我认为用另一种方式来做并不难。该代码已经过 10,000 多人的测试,因此它是一个很好的起点。

替换我正在使用的 STL 结构没有问题,因为它仅限于向量。我使用堆函数(make_heap 和 push_heap)将其中一个向量用作优先级队列。你可以用我的旧 C 代码替换它,它有一个用 C 实现的优先级队列,应该直接放入你的代码中。(它只做一个分配,所以你可以用一个指向你的内存保留区域的指针来替换它。

正如您在标头中的代码片段中看到的那样,C 代码的主要区别在于没有 this 指针,也没有对象,因此您的代码通常将对象指针作为第一个参数。

void PQueueInitialise( PQUEUE *pq, int32 MaxElements, uint32 MaxRating, bool32 bIsAscending );
void PQueueFree( PQUEUE *pq );
int8 PQueuePush( PQUEUE *pq, void *item,  uint32 (*PGetRating) ( void * ) );
int32 PQueueIsFull( PQUEUE *pq );
int32 PQueueIsEmpty( PQUEUE *pq );
void *PQueuePop( PQUEUE *pq, uint32 (*PGetRating) ( void * ) );
于 2009-04-19T16:34:53.720 回答
0

为什么不先在台式机上编写,考虑到编译器的限制,调试它,确保它完美运行,然后才转移到嵌入式环境?

于 2009-04-19T14:11:55.263 回答
0

在进行嵌入式工作时,我曾经什至无法将 C 运行时链接到内存限制,但是硬件有一个 DMA(动态内存分配器)指令,所以我用那个硬件编写了自己的 malloc,你的硬件可能具有类似的功能,所以您可以编写一个 malloc,然后基于 malloc 编写一个新的。

无论如何,最后我使用了 99% 的堆栈分配,并且一些限制设置了我将通过就地构建来回收的 os 静态对象。这可能是一个很好的解决方案。

于 2009-04-19T15:22:07.793 回答