Подтвердить что ты не робот

Использование С++ во встроенных системах

Какие функции С++ следует избегать во встроенных системах?

Пожалуйста, классифицируйте ответ по такой причине, как:

  • Использование памяти
  • размер кода
  • скорость
  • портативность

РЕДАКТИРОВАТЬ: позволяет использовать ARM7TDMI с RAM объемом 64 КБ в качестве цели для контроля объема ответов.

4b9b3361

Ответ 1

RTTI и обработка исключений:

  • Увеличивает размер кода
  • Снижает производительность.
  • Часто можно заменить более дешевые механизмы или лучший программный дизайн.

Шаблоны:

  • Будьте осторожны с ними, если проблема с размером кода. Если у вашего целевого процессора нет или только очень крошечный кеш-сервер, он также может снизить производительность. (шаблоны имеют тенденцию к раздуванию кода, если используются без посторонней помощи). Otoh умное мета-программирование также может уменьшить размер кода. На него нет четкого ответа.

Виртуальные функции и наследование:

  • Это нормально для меня. Я пишу почти весь свой встроенный код в C. Это не мешает мне использовать таблицы указателей функций для имитации виртуальных функций. Они никогда не становились проблемой.

Ответ 2

Выбор, чтобы избежать определенных функций, всегда должен определяться количественным анализом поведения вашего программного обеспечения, вашего оборудования, с выбранной вами инструментальной цепочки, в соответствии с ограничениями, которые ваш домен влечет за собой. В разработке на С++ существует много общепринятой мудрости, основанной на суевериях и древней истории, а не на жестких данных. К сожалению, это часто приводит к большому количеству дополнительного кода обходного пути, который записывается, чтобы избежать использования функций, с которыми у кого-то где-то возникала проблема с раз за разом.

Ответ 3

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

Посмотрите здесь для довольно хорошего примера стандарта кодирования, написанного для встроенного С++.

Ответ 5

Интересно прочитать для Rationale на раннем Встроенный С++ standpard

Смотрите статью на EС++.

Встраиваемый С++ std был правильным подмножеством С++, то есть он не имеет дополнений. Были удалены следующие языковые функции:

  • Множественное наследование
  • Виртуальные базовые классы
  • Информация о времени выполнения (typeid)
  • Новые стили (static_cast, dynamic_cast, reinterpret_cast и const_cast)
  • Определяемый тип классификатора
  • Пространства имен
  • Исключения
  • Шаблоны

Он отметил на странице вики-страницу, о которой говорит Бьярне Страуструп (из стандарта EС++): "Насколько я знаю, EС++ мертв (2004), и если это не так, то должно быть". Далее Страуструп рекомендует рекомендовать документ, на который ссылается ответ Пракаша.

Ответ 6

Используя ARM7 и предполагая, что у вас нет внешнего MMU, проблемы с распределением динамической памяти могут быть сложнее отладить. Я бы добавил "разумное использование нового /delete/free/malloc " в список рекомендаций.

Ответ 7

Если вы используете ARM7TDMI, избегайте unaligned memory accesses любой ценой.

Основное ядро ​​ARM7TDMI не имеет проверки выравнивания и вернет повернутые данные, когда вы выполняете нестандартное чтение. В некоторых реализациях есть дополнительные схемы для создания исключения ABORT, но если у вас нет одной из этих реализаций, поиск ошибок из-за неприсоединения доступа очень болезнен.

Пример:

const char x[] = "ARM7TDMI";
unsigned int y = *reinterpret_cast<const unsigned int*>(&x[3]);
printf("%c%c%c%c\n", y, y>>8, y>>16, y>>24);
  • На процессоре x86/x64 это печатает "7TDM".
  • В CPU SPARC это сбрасывает ядро ​​с ошибкой шины.
  • На процессоре ARM7TDMI это может напечатать что-то вроде "7ARM" или "ITDM", если предположить, что переменная "x" выровнена на 32-битной границе (которая зависит от того, где находится "x" и какие параметры компилятора находятся в использовании и т.д.), и вы используете режим little-endian. Это поведение undefined, но в значительной степени гарантированно не работает так, как вы хотите.

Ответ 8

В большинстве систем вы не хотите использовать new/ delete, если вы не переопределили их своей собственной реализацией, которая вытаскивает из вашей собственной управляемой кучи. Да, это будет работа, но вы имеете дело с системой с ограниченной памятью.

Ответ 9

Я бы не сказал там жесткое и быстрое правило; это сильно зависит от вашего приложения. Встроенные системы обычно:

  • Более ограниченный объем доступной памяти
  • Часто работают на более медленном оборудовании
  • Скорее, ближе к оборудованию, т.е. каким-то образом водить его, как возиться с настройками регистра.

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

Ответ 10

Что касается раздувания кода, я думаю, что виновник, скорее всего, будет inline, чем шаблоны.

Например:

// foo.h
template <typename T> void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

Компонент, скорее всего, объединит все определения "foo" в одну единицу перевода. Поэтому размер "foo" ничем не отличается от размера любой другой функции пространства имен.

Если ваш компоновщик не делает этого, вы можете использовать для него конкретное создание:

// foo.h
template <typename T> void foo ();

// foo.cc
#include "foo.h"
template <typename T> void foo () { /* some relatively large definition */ }
template void foo<int> ();        // Definition of 'foo<int>' only in this TU

// b1.cc
#include "foo.h"
void b1 () { foo<int> (); }

// b2.cc
#include "foo.h"
void b2 () { foo<int> (); }

// b3.cc
#include "foo.h"
void b3 () { foo<int> (); }

Теперь рассмотрим следующее:

// foo.h
inline void foo () { /* some relatively large definition */ }

// b1.cc
#include "foo.h"
void b1 () { foo (); }

// b2.cc
#include "foo.h"
void b2 () { foo (); }

// b3.cc
#include "foo.h"
void b3 () { foo (); }

Если компилятор решает ввести в строку 'foo' для вас, тогда вы получите 3 разных копии 'foo'. Нет шаблонов в поле зрения!

EDIT: Из комментария выше из InSciTek Jeff

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

// a.h
template <typename T>
class A
{
public:
  void f1(); // will be called 
  void f2(); // will be called 
  void f3(); // is never called
}


// a.cc
#include "a.h"

template <typename T>
void A<T>::f1 () { /* ... */ }

template <typename T>
void A<T>::f2 () { /* ... */ }

template <typename T>
void A<T>::f3 () { /* ... */ }

template void A<int>::f1 ();
template void A<int>::f2 ();

Если ваша цепочка инструментов полностью не сломана, выше будет генерировать код только для 'f1' и 'f2'.

Ответ 11

Функции времени обычно зависят от ОС (если вы не переписываете их). Используйте свои собственные функции (особенно если у вас есть RTC)

шаблоны подходят для использования, если у вас достаточно места для кода - otwerise не использует их

исключения не очень переносимы также

printf функции, которые не пишут в буфер, не являются переносимыми (вам необходимо каким-то образом подключиться к файловой системе, чтобы писать в файл * с printf). Используйте только функции sprintf, snprintf и str * (strcat, strlen) и, конечно же, их широкие char ответчики (wcslen...).

Если проблема в скорости - возможно, вы должны использовать свои собственные контейнеры, а не STL (например, контейнер std:: map, чтобы убедиться, что ключ равен, 2 (да 2) сравнение с оператор "меньше" (a [меньше] b == false && b [меньше] a == false mean a == b). 'less' - единственный сравнительный параметр, полученный классом std:: map (и не только). Это может привести к некоторой потере производительности в критических процедурах.

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

Функции распределения памяти, вероятно, должны быть переписаны также потому, что они зависят от ОС во многих отношениях (особенно при работе с распределением памяти по потоку).

malloc использует переменную _end (обычно объявленную в компоновщике script) для выделения памяти, но это не является безопасным потоком в "неизвестных" средах.

иногда вам следует использовать Thumb, а не режим Arm. Это может повысить производительность.

Итак, для 64-разрядной памяти я бы сказал, что С++ с некоторыми из его приятных функций (STL, исключения и т.д.) может быть излишним. Я бы определенно выбрал C.

Ответ 12

Используя как компилятор GCC ARM, так и собственный SDT ARM, я бы добавил следующие комментарии:

  • ARM SDT производит более быстрый, быстрый код, но очень дорого ( > Eur5k на сиденье!). На моей предыдущей работе мы использовал этот компилятор, и все было в порядке.

  • Инструменты GCC ARM работают очень хорошо хотя и это то, что я использую самостоятельно проектов (GBA/DS).

  • Используйте режим "большой палец", так как это уменьшает размер кода значительно. В 16-битных вариантах шины ARM (например, GBA) есть также преимущество в скорости.

  • 64k серьезно мало для С++ развитие. Я бы использовал C и Assembler в этой среде.

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

Ответ 13

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

Ответ 14

Для встроенных систем вы преимущественно хотите избегать вещей, которые имеют определенную ненормальную стоимость исполнения. Некоторые примеры: исключения и RTTI (включить dynamic_cast и typeid).

Ответ 15

Убедитесь, что вы знаете, какие функции поддерживаются компилятором для встроенной платформы, а также убедитесь, что вы знаете особенности своей платформы. Например, компилятор TI CodeComposer не выполняет автоматические экземпляры шаблонов. В результате, если вы хотите использовать сортировку STL, вам необходимо создать пять разных вещей вручную. Он также не поддерживает потоки.

Другим примером является то, что вы можете использовать чип DSP, который не имеет аппаратной поддержки операций с плавающей запятой. Это означает, что каждый раз, когда вы используете float или double, вы оплачиваете стоимость вызова функции.

Подводя итог, узнайте все, что вам нужно знать о встроенной платформе и компиляторе, а затем вы узнаете, какие функции следует избегать.

Ответ 16

Одна особая проблема, которая меня удивила с ATMega GCC 3.something: когда я добавила виртуальную функцию ember в один из моих классов, мне пришлось добавить виртуальный деструктор. В этот момент компоновщик попросил удалить operator (void *). Я понятия не имею, почему это происходит, и добавление пустого определения для этого оператора решило проблему.

Ответ 17

Обратите внимание, что стоимость исключений зависит от вашего кода. В одном приложении я профилировал (относительно небольшой на ARM968), поддержка исключений добавила 2% к времени выполнения, а размер кода был увеличен на 9,5 КБ. В этом приложении исключения были выброшены только в том случае, если произошло что-то серьезно плохое, то есть никогда на практике, - которое слишком быстро сокращало время выполнения.