Как можете да правите 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-битовите Atmel нямат достатъчно RAM, за да мислят дори за динамична памет a'la malloc/new.   -  person lothar    schedule 19.04.2009
comment
Ако мога да претоваря тези оператори, тогава мога да контролирам откъде идва паметта. Може да бъде на купчината или стека.   -  person Bernard Igiri    schedule 19.04.2009
comment
Разбрах, че имам само 1KB за игра, което по същество изключва A* като жизнеспособен търсач на пътя за моя робот. Сега използвам обикновен DFS, докато не получа такъв с повече RAM.   -  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++, според мен трябва да е лесно да пренесете c++ кода в c. Един трик, който намирам за полезен, е да преименувам всеки конструктор от NAME на new_NAME. Направете същото за деструкторите. - person Trevor Boyd Smith; 20.04.2009

Мисля, че подхождате към проблема от гледна точка, която не е оптимална.

Фокусирате се върху компилатора (или липсата на такъв), вместо да се фокусирате върху ХАРДУЕРА.

Най-вероятният отговор на основните ви въпроси е "защото хардуерът не поддържа всички тези C++ неща". Вграденият хардуер (микроконтролери) се отличава с персонализирането на хардуерния дизайн - карти на паметта, манипулатори на прекъсвания, I/O и др.

Според мен ПЪРВО трябва да прекарате известно време с книгата за хардуера за микроконтролера, като научите тънкостите на устройството - т.е. как е проектирано и за каква основна цел. Някои са предназначени за бързо манипулиране на паметта, някои за бързо I/O управление, някои за A/D тип работа, някои за обработка на сигнали. Типът микроконтролер диктува инструкциите за асемблер, които те са написали за него, и това диктува какво всеки компилатор от по-високо ниво може да прави ефективно.

Ако това е важно, отделете малко време, за да разгледате и асемблера - той ще ви каже какво са сметнали дизайнерите за важно. Освен това ще ви каже много за това колко можете да получите от компилатор на високо ниво.

Като цяло микроконтролерите не поддържат C++, защото дизайнът наистина не се интересува от обекти или фантастична работа с памет (от гледна точка на C++). Може да се направи, но често се опитвате да забиете кръгло колче в квадратна дупка, за да накарате конструкторите и деструкторите (и „нови“ и „изтриване“) да работят в микросредата.

АКО имате C компилатор за това устройство, считайте го за благословия. Един добър C компилатор често е "повече от достатъчен" за създаване на отличен вграден софтуер.

наздраве,

-Ричард

person Huntrods    schedule 19.04.2009

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

Ако не поддържа „ново“, тогава вероятно е, защото няма смисъл да се прави автоматично разграничение между купчина и стек. Това може да се дължи на конфигурацията на вашата памет. Може да се дължи и на това, че ресурсите на паметта са толкова ограничени, че само много внимателно разпределение има смисъл. Ако абсолютно трябва да внедрите свой собствен „нов“ оператор, може да помислите за адаптиране на Doug Lea's malloc. Вярвам, че той е започнал своя разпределител при подобни обстоятелства (преимплементира новото на C++).

Обичам 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 кода е, че няма този указател, няма обект, така че вашият код обикновено приема указател на обект като първи аргумент.

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/безплатно)! - person mmmmmmmm; 19.04.2009
comment
Ако го разгледате по-внимателно, той извиква malloc, за да разпредели блок памет за опашка с приоритет с фиксиран размер. След това самата опашка използва този единичен блок много ефективно. Така че може да се преобразува да не използва malloc с един ред код. - person justinhj; 19.04.2009
comment
Кой гласува против това? Това са полезни неща, аз също се нуждая от помощ с A*. Имам малко код на Google и книга за AI, но все още измислям всичко. - person Bernard Igiri; 19.04.2009

Защо не го напишете първо на вашия настолен компютър, като вземете предвид ограниченията на компилатора, да го дебъгвате, да се уверите, че работи перфектно и едва след това да преминете към вградената среда?

person shoosh    schedule 19.04.2009
comment
Не мисля, че това е много добра идея, защото има толкова много ограничения и разлики на вградените платформи (малко RAM, 8 бита вместо 32 бита), така че никога няма да го накарате да работи по този начин. - person mmmmmmmm; 19.04.2009

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

Както и да е, в крайна сметка използвах 99% разпределение на стека и няколко ограничения задават статични обекти, които бих рециклирал, като изграждам на място. Това може да е добро решение за мен.

person Robert Gould    schedule 19.04.2009