Есть ли причина использовать C вместо C ++ для разработки встраиваемых систем? - PullRequest
77 голосов
/ 01 мая 2009

Вопрос

На моем оборудовании есть два компилятора C ++ и C89

Я думаю об использовании C ++ с классами, но без полиморфизма (чтобы избежать vtables). Основные причины, по которым я хотел бы использовать C ++:

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

Видите ли вы какую-либо причину придерживаться C89 при разработке для очень ограниченного оборудования (4 КБ ОЗУ)?

Заключение

Спасибо за ваши ответы, они были действительно полезны!

Я обдумал тему, и я буду придерживаться C главным образом потому, что:

  1. Проще предсказать фактический код на C, и это действительно важно, если у вас есть только 4 КБ ОЗУ.
  2. Моя команда состоит в основном из разработчиков C, поэтому расширенные возможности C ++ не будут часто использоваться.
  3. Я нашел способ встроить функции в мой компилятор C (C89).

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

Ответы [ 30 ]

63 голосов
/ 02 мая 2009

Для цели с очень ограниченным ресурсом, такой как 4 КБ ОЗУ, я бы протестировал воды с некоторыми образцами, прежде чем прилагать много усилий, которые не могут быть легко перенесены обратно в чистую реализацию ANSI C ,

Рабочая группа по Embedded C ++ предложила стандартное подмножество языка и стандартное подмножество стандартной библиотеки. К сожалению, я потерял следы этих усилий, когда умер журнал пользователя C. Похоже, что в Википедии есть статья, и комитет все еще существует.

Во встроенной среде вам действительно нужно быть осторожным с распределением памяти. Для обеспечения такой заботы вам может потребоваться определить глобальный operator new() и его друзей для чего-то, что нельзя даже связать, чтобы вы знали, что оно не используется. С другой стороны, размещение new, вероятно, будет вашим другом, если его использовать разумно вместе со стабильной, поточно-ориентированной и гарантированной задержкой схемой размещения.

Встроенные функции не вызовут особых проблем, если только они не будут достаточно большими, чтобы они должны были быть истинными функциями. Конечно, у макросов, которые они заменяли, была та же проблема.

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

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

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

В небольшой встроенной среде вы будете либо напрямую подключаться к ядру реального времени, либо работать непосредственно на оборудовании. В любом случае вам нужно убедиться, что ваш код запуска во время выполнения правильно обрабатывает специфичные для C ++ задачи по запуску. Это может быть так же просто, как использование правильных опций компоновщика, но поскольку обычно прямой контроль над источником осуществляется с точкой входа при перезагрузке, вам может потребоваться проверить это, чтобы убедиться, что он выполняет все. Например, на платформе ColdFire, над которой я работал, инструменты dev поставлялись с модулем CRT0.S, в котором присутствовали инициализаторы C ++, но комментировались. Если бы я использовал его прямо из коробки, я был бы озадачен глобальными объектами, конструкторы которых вообще никогда не запускались.

Кроме того, во встроенной среде часто необходимо инициализировать аппаратные устройства, прежде чем их можно будет использовать, и если нет ОС и загрузчика, то это делает ваш код. Вам нужно помнить, что конструкторы для глобальных объектов запускаются до вызова main(), поэтому вам нужно будет изменить локальный CRT0.S (или его эквивалент), чтобы выполнить аппаратную инициализацию до сами глобальные конструкторы называются. Очевидно, что вершина main() слишком поздняя.

42 голосов
/ 02 мая 2009

Две причины использования C над C ++:

  1. Для многих встроенных процессоров либо нет компилятора C ++, либо вы должны доплатить за него.
  2. Мой опыт показывает, что значительная часть инженеров по встроенному программному обеспечению практически не имеет опыта работы с C ++ - либо из-за (1), либо из-за того, что его не преподают по специальностям электронной инженерии, - и поэтому было бы лучше придерживаться того, что они знают.

Кроме того, в исходном вопросе и ряде комментариев упоминается 4 КБ RAM . Для типичного встроенного процессора объем ОЗУ (в основном) не связан с размером кода, поскольку код хранится и запускается с флэш-памяти.

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

Об использовании подмножества C ++ для использования со встроенными системами: теперь существует стандарт MISRA C ++ , который, возможно, стоит посмотреть.

РЕДАКТИРОВАТЬ: См. Также этот вопрос , что привело к дискуссии о C против C ++ для встроенных систем.

25 голосов
/ 01 мая 2009

Нет. Любой из особенностей языка C ++, которые могут вызвать проблемы (полиморфизм времени выполнения, RTTI и т. Д.), Можно избежать при выполнении встроенной разработки. Существует сообщество разработчиков встраиваемых C ++ (я помню чтение колонок разработчиками встраиваемых систем, использующих C ++ в старом журнале пользователей C / C ++), и я не могу себе представить, что они были бы очень громкими, если бы выбор был таким плохим. *

20 голосов
/ 02 мая 2009

Технический отчет о производительности C ++ - отличное руководство для такого рода вещей. Обратите внимание, что в нем есть раздел, посвященный проблемам встроенного программирования!

Также ++ об упоминании Embedded C ++ в ответах. Стандарт не на 100%, на мой вкус, но это хороший пример, когда вы решаете, какие части C ++ вы можете удалить.

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

Тем не менее, ваш друг является компоновщиком: проверяйте его часто, и вы быстро обнаружите источники кода и статическую память.

После этого применяются стандартные соображения об использовании динамической памяти: в такой ограниченной среде, как та, о которой вы упомянули, вы можете вообще не использовать динамическое распределение. Иногда вы можете сойти с пулами памяти для небольших динамических распределений или «основанного на кадрах» выделения, когда вы предварительно выделяете блок и выбрасываете все это позже.

16 голосов
/ 01 мая 2009

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

Вы можете использовать классы C ++ и т. Д., Просто

  • Ограничьте использование виртуальных функций (как вы сказали)
  • Ограничьте использование шаблонов
  • Для встроенной платформы вам нужно переопределить оператор new и / или использовать размещение new для выделения памяти.
14 голосов
/ 18 октября 2009

Как инженер по встроенному ПО / встроенным системам, я могу рассказать вам о причине, почему C по-прежнему является выбором № 1 по сравнению с C ++, и да, я свободно владею обоими.

1) Некоторые цели, для которых мы разрабатываем, имеют 64 КБ ОЗУ как для кода, так и для данных, поэтому вы должны убедиться, что каждый байт имеет значение, и да, я занимался оптимизацией кода, чтобы сэкономить 4 байта, что стоило мне 2 часа, и это в 2008 году.

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

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

4) Приведение и даже упаковка (когда структура невыровненных данных пересекает границу слова) необходимы из-за ограниченной конструкции оборудования (то есть механизма ECC, который подключен определенным образом) или из-за аппаратной ошибки. Вы не можете предположить слишком много в неявном виде, так почему объект слишком сильно ориентирует его?

5) Сценарий наихудшего случая: устранение некоторых объектно-ориентированных методов заставит разработчиков задуматься, прежде чем использовать ресурсы, которые могут взорваться (т. Е. Выделять 512 байт в стеке, а не из буфера данных), и предотвратить некоторые из потенциально худших сценарий, в котором не проверяется или не удаляется весь путь кода вместе.

6) Мы используем много абстракций, чтобы защитить аппаратное обеспечение от программного обеспечения и сделать код максимально переносимым и удобным для моделирования. Аппаратный доступ должен быть заключен в макрос или встроенную функцию, которые условно компилируются между различными платформами, тип данных должен быть приведен как размер байта, а не как целевой, прямое использование указателя не допускается (поскольку некоторые платформы предполагают, что отображенный в памяти ввод-вывод является так же, как память данных) и т. д.

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

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

- какая-то прошивка, парень от SanDisk.

10 голосов
/ 15 марта 2010

Мои личные предпочтения - C, потому что:

  • Я знаю, что делает каждая строка кода (и сколько стоит)
  • Я недостаточно хорошо знаю C ++, чтобы знать, что делает (и стоит) каждая строка кода

Почему люди так говорят? Вы не знаете, что делает каждая строка C, если вы не проверили вывод asm. То же самое касается C ++.

Например, что asm производит это невинное утверждение:

a[i] = b[j] * c[k];

Это выглядит довольно невинно, но компилятор на основе gcc создает этот ассемблер для 8-битного микро

CLRF 0x1f, ACCESS
RLCF 0xfdb, W, ACCESS
ANDLW 0xfe
RLCF 0x1f, F, ACCESS
MOVWF 0x1e, ACCESS
MOVLW 0xf9
MOVF 0xfdb, W, ACCESS
ADDWF 0x1e, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfa
MOVF 0xfdb, W, ACCESS
ADDWFC 0x1f, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0xfee, 0x1c
NOP
MOVFF 0xfef, 0x1d
NOP
MOVLW 0x1
CLRF 0x1b, ACCESS
RLCF 0xfdb, W, ACCESS
ANDLW 0xfe
RLCF 0x1b, F, ACCESS
MOVWF 0x1a, ACCESS
MOVLW 0xfb
MOVF 0xfdb, W, ACCESS
ADDWF 0x1a, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfc
MOVF 0xfdb, W, ACCESS
ADDWFC 0x1b, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0xfee, 0x18
NOP
MOVFF 0xfef, 0x19
NOP
MOVFF 0x18, 0x8
NOP
MOVFF 0x19, 0x9
NOP
MOVFF 0x1c, 0xd
NOP
MOVFF 0x1d, 0xe
NOP
CALL 0x2142, 0
NOP
MOVFF 0x6, 0x16
NOP
MOVFF 0x7, 0x17
NOP
CLRF 0x15, ACCESS
RLCF 0xfdf, W, ACCESS
ANDLW 0xfe
RLCF 0x15, F, ACCESS
MOVWF 0x14, ACCESS
MOVLW 0xfd
MOVF 0xfdb, W, ACCESS
ADDWF 0x14, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfe
MOVF 0xfdb, W, ACCESS
ADDWFC 0x15, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0x16, 0xfee
NOP
MOVFF 0x17, 0xfed
NOP

Количество производимых инструкций в значительной степени зависит от:

  • Размеры a, b и c.
  • хранятся ли эти указатели в стеке или являются глобальными
  • находятся ли i, j и k в стеке или являются глобальными

Это особенно верно в крошечном встроенном мире, где процессоры просто не настроены на обработку C. Поэтому мой ответ будет таким: C и C ++ так же плохи, как и другие, если вы не всегда проверяете вывод asm, в в этом случае они так же хороши, как друг друга.

Hugo

8 голосов
/ 01 мая 2009

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

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

7 голосов
/ 01 мая 2009

Не вижу смысла использовать C вместо C ++. Что бы вы ни делали в C, вы можете делать это и в C ++. Если вы хотите избежать накладных расходов на VMT, не используйте виртуальные методы и полиморфизм.

Однако C ++ может предоставить некоторые очень полезные идиомы без лишних затрат. Один из моих любимых это RAII. Классы не обязательно дорогие с точки зрения памяти или производительности ...

6 голосов
/ 01 мая 2009

Я написал код для встроенной в ARM7 paltform в IAR Workbench. Я настоятельно рекомендую полагаться на шаблоны для оптимизации времени компиляции и прогнозирования пути. Избегайте динамического заброса, как чума. Используйте черты / политики в ваших интересах, как предписано в книге Андрея Александреску, Современный дизайн C ++ .

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

...