Как вы можете сделать C ++, если у вашего встроенного компилятора нет оператора new или поддержки STL? - PullRequest
6 голосов
/ 19 апреля 2009

Я работаю над старшим проектом группы для моего университета, и я столкнулся с серьезным препятствием в попытке заставить мой код работать.

Компилятор, который мы имеем для нашего 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) Выделите и найдите какой-нибудь хак для вызова конструктора для объектов, как только я выделю для них место. Размещение нового не вариант, так как новый не оператор. 3) Просто используйте C и смиритесь с этим, это микроконтроллер, почему я начинаю фантазировать? 4) Найдите лучший компилятор, который, вероятно, будет стоить $$$.

Второй вариант самый сложный, но он принесет наибольшую отдачу с точки зрения того, как я могу написать этот код. Тем не менее, я думаю, что отладка может быть огромной болью, если я ошибаюсь. Я думаю о создании объектов в стеке, копировании их битов в выделенное пространство, а затем обнулении битов в объекте, чтобы он не вызывал его деструктор. Чтобы сделать это, я бы получал доступ к битам напрямую с помощью беззнакового указателя на символ и оператора sizeof для получения количества байтов.

Звучит ужасно, и я не знаю, может ли это работать надежно, но я обдумываю это. Я знаю, что vtables может быть проблемой, но я не собираюсь иметь какие-либо vtables, поскольку это всего лишь 8-битный микроконтроллер.

Ответы [ 8 ]

23 голосов
/ 19 апреля 2009

Не борись со своими инструментами. Если единственный компилятор для вашей встроенной системы - это компилятор C, изучите C - это не сложно. Попытка создать какую-то убогую версию двух языков только для решения довольно простой проблемы программирования конец в слезах.

Если посмотреть по-другому, если ваша встроенная платформа даже не поддерживает компилятор C, а только ассемблер, будет ли ваш первый импульс сесть и написать компилятор C ++ на ассемблере? Я надеюсь, что нет, я надеюсь, что вы бы вместо этого сесть и научиться использовать ассемблер для выполнения вашего задания - написание компилятора C ++ (или даже компилятора C) было бы совершенно неуместным использованием вашего времени и почти наверняка привело бы к сбою.

10 голосов
/ 19 апреля 2009

Только для записи, обнуление битов в объекте не повлияет на то, будет ли вызван деструктор (если только у компилятора нет особой причуды, которая допускает такое поведение). Просто напишите несколько операторов логирования в деструкторе, чтобы проверить это.

Структурирование вашей программы, чтобы ничего не распределять, вероятно, так, как была спроектирована система. Раньше я не работал со встроенными системами, однако я читал некоторые опытные магазины встроенных систем, которые не рекомендуют использовать динамическую память, потому что среда выполнения имеет ограниченные возможности.


Однако, если необходимо, вы все равно можете использовать новое место размещения. Если у вас нет заголовка <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() { }

Вставьте это где-нибудь в заголовочный файл, включенный в каждый исходный файл, который использует размещение new / delete.

Пример файла, который проверяет это:

#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 / delete, с которым вы знакомы, без необходимости использования libstdc++.

Удачи!

6 голосов
/ 19 апреля 2009

Я думаю, что вы подходите к проблеме с точки зрения, которая ниже оптимальной.

Вы сосредотачиваетесь на компиляторе (или его отсутствии) вместо того, чтобы сосредотачиваться на ТС.

Наиболее вероятный ответ на ваши основные вопросы - «потому что аппаратное обеспечение не поддерживает все эти вещи C ++». Встроенные аппаратные средства (микроконтроллеры) отмечены для настройки аппаратного дизайна - карты памяти, обработчики прерываний, ввод-вывод и т. Д.

По моему мнению, вам следует ПЕРВЫЙ потратить некоторое время на книгу по аппаратному обеспечению для микроконтроллера, изучив все входы и выходы устройства - то есть, как оно было разработано и для какой цели. Некоторые были разработаны для быстрой обработки памяти, некоторые для быстрой обработки ввода / вывода, некоторые для работы типа A / D, некоторые для обработки сигналов. Тип микроконтроллера диктует инструкции ассемблера, которые они написали для него, и это диктует, что любой компилятор более высокого уровня может сделать эффективно.

Если это важно, потратьте некоторое время и на ассемблера - он расскажет вам, что дизайнеры посчитали важным. Он также расскажет вам многое о том, сколько вы можете получить от высокоуровневого компилятора.

Как правило, микроконтроллеры не поддерживают C ++, потому что дизайн действительно не заботится об объектах или необычной обработке памяти (с точки зрения C ++). Это можно сделать, но вы часто пытаетесь вбить круглый колышек в квадратное отверстие, чтобы конструкторы и деструкторы (а также 'new' и 'delete') работали в микросреде.

Если у вас есть компилятор C для этого устройства, считайте это благословением. Хороший компилятор C часто "более чем достаточен" для создания превосходного встроенного программного обеспечения.

Приветствия

-Ричард

4 голосов
/ 19 апреля 2009

Если у вас нет этих инструментов, это не значит, что вы не сможете воспользоваться C ++. Если проект достаточно велик, достаточно мотивации для доступа к объектно-ориентированному дизайну.

Если он не поддерживает 'new', то, вероятно, потому, что не имеет смысла автоматически различать кучу и стек. Это может быть из-за конфигурации вашей памяти. Это также может быть связано с тем, что ресурсы памяти настолько ограничены, что имеет смысл только очень осторожное распределение. Если вам абсолютно необходимо реализовать свой собственный «новый» оператор, вы можете попробовать адаптировать malloc Дуга Ли. Я полагаю, что он начал свой распределитель в аналогичных обстоятельствах (переопределение нового C ++).

Мне нравится STL, но все еще можно делать полезные вещи без него. В зависимости от масштаба проекта вам может быть лучше использовать массив.

2 голосов
/ 19 апреля 2009

У меня был похожий компилятор, который реализовал причудливую версию Embedded-C ++ стандарта . У нас было operator new, которое для нас вызывало бы конструкторы, а деструкторы назывались , в большинстве случаев . Поставщик компилятора / среды выполнения выполнил и внедрил try и catch, используя setjmp и longjmp для удобства для инженера . Проблема заключалась в том, что они никогда не упоминали, что throw не приведет к вызову деструкторов локальных объектов!

В любом случае, наша группа унаследовала кодовую базу после того, как кто-то написал приложение, действующее так, как будто оно было стандартным C ++: с использованием методов RAII и всего остального. В итоге мы переписали его так, что некоторые из нас называют взамен объектно-ориентированный C . Возможно, вы захотите просто укусить пулю и написать прямо на C. Вместо конструкторов используйте явно вызванный метод инициализации. Деструкторы становятся явно вызванным методом завершения. В C ++ не так много всего, что вы не можете имитировать в C довольно быстро. Да, МИ - это боль ... но одиночное наследование довольно легко. Взгляните на этот PDF для некоторых идей. Это почти описывает подход, который мы выбрали. Я действительно хотел бы, чтобы я где-то записал наш метод ...

1 голос
/ 19 апреля 2009

Вы можете найти полезный код на моем A * учебном сайте . Хотя код , который я написал для поддержки этого, использует STL, легко исключить поддержку STL. Кроме того, в него включен распределитель пулов (fsa.h), который я написал для ускорения STL на игровых приставках. Это код C ++, но я портировал его изначально из C, и я не думаю, что было бы трудно сделать это другим способом. Код протестирован более чем 10000 человек, так что это хорошая база для начала.

Замена структур STL, которые я использую, не проблема, поскольку она ограничена векторами. Я использую один из векторов в качестве приоритетной очереди, используя функции кучи (make_heap и push_heap). Вы можете заменить это моим старым кодом C , в котором реализована очередь приоритетов, реализованная в C, которая должна просто попасть в ваш код. (Что делает только один alloc, так что вы можете заменить его указателем на зарезервированную область вашей памяти.

Как вы можете видеть в этом фрагменте кода из заголовка, основное отличие в коде C состоит в том, что здесь нет ни этого указателя, ни объекта, поэтому ваш код обычно принимает указатель объекта в качестве первого аргумента.

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 * ) );
0 голосов
/ 19 апреля 2009

при выполнении встроенной работы я однажды даже не мог связать среду выполнения C с ограничениями памяти, но на оборудовании была инструкция DMA (динамический распределитель памяти), поэтому я написал свой собственный malloc с этим оборудованием, ваше оборудование, вероятно, имеет аналогичное функция, так что вы можете написать malloc, а затем новый на основе malloc.

В любом случае, в конце концов, я использовал 99% распределения стека, и несколько ограничений устанавливают статические объекты, которые я буду перерабатывать, создавая их на месте. Это может быть хорошим решением.

0 голосов
/ 19 апреля 2009

Почему бы сначала не написать его на настольном компьютере, принимая во внимание ограничения компилятора, отладить его, убедиться, что он работает идеально, и только затем переходить во встроенную среду?

...