Как вы можете использовать C++, если ваш встроенный компилятор не поддерживает оператор new или STL?

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

Компилятор, который у нас есть для нашего 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) Найдите лучший компилятор, который, вероятно, будет стоить $$$.

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

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


person Bernard Igiri    schedule 19.04.2009    source источник
comment
Если я правильно помню, у 8-битных Atmels недостаточно оперативной памяти, чтобы даже думать о динамической памяти a'la malloc/new.   -  person lothar    schedule 19.04.2009
comment
Если я смогу перегрузить эти операторы, то смогу контролировать, откуда берется память. Он может быть в куче или стеке.   -  person Bernard Igiri    schedule 19.04.2009
comment
Я обнаружил, что у меня есть только 1 КБ для игры, что по существу исключало A * как жизнеспособный поиск пути для моего робота. Сейчас я использую простую DFS, пока не смогу получить другую с большим объемом оперативной памяти.   -  person Bernard Igiri    schedule 09.05.2009


Ответы (8)


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

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


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

Удачи!

person Chris Jester-Young    schedule 19.04.2009
comment
Вау, похоже, это работает. Я не мог использовать предоставленные вами заголовки, но эти операторские функции, похоже, работают. Из того, что я читал об этих операторах, new[] и delete[] нужно знать размер массива для вызова всех необходимых деструкторов, но в моем тесте вызывались все необходимые деструкторы. - person Bernard Igiri; 19.04.2009

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

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

person Community    schedule 19.04.2009
comment
Хотя я согласен, что бороться с инструментами глупо, вложения в разработку инструментов обычно окупаются. Если и когда я использовал ассемблер, написание небольшого DSL на основе препроцессора стоило того. Однако я бы никогда не дошел до написания компилятора C++ :) - person Robert Gould; 19.04.2009
comment
О, я согласен, и когда я писал ассемблер (давным-давно), я использовал библиотеки макросов, которые я написал. И я действительно написал половину компилятора C на ассемблере Z80 (хотя и не для решения конкретной проблемы), прежде чем пришел в себя, переписал его на C и перекрестно скомпилировал. Это все еще не работает, но работы стало намного меньше :-) - person ; 19.04.2009
comment
И не забывайте, что большинство встраиваемых компиляторов C поставляются только с очень ограниченным подмножеством библиотеки C. Я думаю, что одной из важных частей, которой им не хватает, является управление кучей (malloc/free). - person mmmmmmmm; 19.04.2009
comment
Мой компилятор поддерживает все C, а в предоставленной ссылке указано, что он поддерживает C++. Единственная проблема в том, что он не поддерживает все. Но мне только что удалось скомпилировать новую перегрузку оператора. У меня нет оборудования, чтобы проверить это, но я думаю, что у меня есть решение. Использование C займет у меня больше времени, так как я гораздо лучше разбираюсь в C++ с RAII и приемами метапрограммирования шаблонов, чем в C. Кроме того, пример кода A*, который у меня есть, — это C++. - person Bernard Igiri; 19.04.2009
comment
Если вам нужна только одна библиотека из С++, на мой взгляд, будет легко перенести код С++ на c. Один трюк, который я считаю полезным, — это переименовать каждый конструктор с NAME на new_NAME. Сделайте то же самое для деструкторов. - person Trevor Boyd Smith; 20.04.2009

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

Вы сосредотачиваетесь на компиляторе (или его отсутствии), а не на АППАРАТНОМ ОБЕСПЕЧЕНИИ.

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

На мой взгляд, вы должны СНАЧАЛА провести некоторое время с аппаратной книгой для микроконтроллера, изучая все тонкости устройства - то есть, как оно было разработано и для какой основной цели. Некоторые из них были разработаны для быстрой работы с памятью, некоторые для быстрой обработки ввода-вывода, некоторые для аналого-цифрового преобразования, некоторые для обработки сигналов. Тип микроконтроллера диктует инструкции ассемблера, которые они написали для него, и это диктует, что может эффективно делать любой компилятор более высокого уровня.

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

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

ЕСЛИ у вас есть компилятор C для этого модуля, считайте это благословением. Хорошего компилятора C часто бывает «более чем достаточно» для создания отличного встраиваемого программного обеспечения.

Ваше здоровье,

-Ричард

person Huntrods    schedule 19.04.2009

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

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

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

person Waylon Flinn    schedule 19.04.2009

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

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

person D.Shawley    schedule 19.04.2009

Вы можете найти полезный код на моем учебном веб-сайте 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 * ) );
person justinhj    schedule 19.04.2009
comment
Ваш старый код C вызывает malloc(). Я не думаю, что существует так много компиляторов C для 8-битных встроенных платформ, которые поддерживают достаточно библиотеки C, чтобы дать вам управление кучей (malloc/free)! - person mmmmmmmm; 19.04.2009
comment
Если вы посмотрите на него более внимательно, он вызывает malloc для выделения блока памяти для приоритетной очереди фиксированного размера. Затем сама очередь очень эффективно использует этот единственный блок. Таким образом, его можно преобразовать, чтобы не использовать malloc, с помощью одной строки кода. - person justinhj; 19.04.2009
comment
Кто проголосовал за это? Это полезный материал, мне тоже нужна помощь A*. У меня есть код Google и книга по ИИ, но я все еще во всем этом разбираюсь. - person Bernard Igiri; 19.04.2009

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

person shoosh    schedule 19.04.2009
comment
Я не думаю, что это очень хорошая идея, потому что на встроенных платформах так много ограничений и различий (мало оперативной памяти, 8 бит вместо 32 бит), поэтому вы никогда не заставите это работать таким образом. - person mmmmmmmm; 19.04.2009

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

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

person Robert Gould    schedule 19.04.2009