После нескольких лет кодирования на C ++ мне недавно предложили работу кодирования на C во встроенной области.
Отложив в сторону вопрос о том, правильно или неправильно отклонять C ++ во встроенном поле, в C ++ есть некоторые особенности / идиомы, которые я бы очень упустил. Просто назвать несколько:
- Общие, типобезопасные структуры данных (с использованием шаблонов).
- RAII. Особенно в функциях с несколькими точками возврата, например, когда не нужно забывать отпускать мьютекс в каждой точке возврата.
- Деструкторы в целом. Т.е. вы пишете d'tor один раз для MyClass, тогда, если экземпляр MyClass является членом MyOtherClass, MyOtherClass не должен явно деинициализировать экземпляр MyClass - его d'tor вызывается автоматически.
- Пространства имен.
Каков ваш опыт перехода с C ++ на C?
Какие замены C вы нашли для своих любимых функций / идиом C ++? Обнаружили ли вы какие-либо функции C, которые вам хотелось бы иметь в C ++?
Ответы:
Работая над встроенным проектом, я однажды попробовал работать на всем C, и просто не выдержал. Это было настолько многословно, что было трудно что-либо прочитать. Кроме того, мне понравились написанные мной оптимизированные для встраиваемых контейнеров, которые должны были превратиться в гораздо менее безопасные и более сложные для исправления
#define
блоки.Код, который на C ++ выглядел так:
if(uart[0]->Send(pktQueue.Top(), sizeof(Packet))) pktQueue.Dequeue(1);
превращается в:
if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet))) Queue_Packet_Dequeue(pktQueue, 1);
что многие люди, вероятно, скажут, что это нормально, но становится нелепым, если вам нужно сделать больше, чем пару вызовов "методов" в строке. Две строки C ++ превратятся в пять строк C (из-за ограничений на длину строки в 80 символов). Оба будут генерировать один и тот же код, поэтому это не похоже на целевой процессор!
Однажды (еще в 1995 году) я попытался написать много C для многопроцессорной программы обработки данных. Такой, где у каждого процессора своя память и программа. Компилятор, предоставленный поставщиком, был компилятором C (своего рода производным от HighC), их библиотеки были с закрытым исходным кодом, поэтому я не мог использовать GCC для сборки, а их API-интерфейсы были разработаны с учетом того, что ваши программы будут в первую очередь инициализировать / процесс / terminate, поэтому взаимодействие между процессорами было в лучшем случае рудиментарным.
У меня было около месяца, прежде чем я сдался, нашел копию cfront и взломал ее в make-файлы, чтобы я мог использовать C ++. Cfront даже не поддерживал шаблоны, но код C ++ был намного понятнее.
Общие, типобезопасные структуры данных (с использованием шаблонов).
Самая близкая вещь C к шаблонам - это объявить файл заголовка с большим количеством кода, который выглядит так:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { /* ... */ }
затем вставьте что-то вроде:
#define TYPE Packet #include "Queue.h" #undef TYPE
Обратите внимание, что это не будет работать для составных типов (например, без очередей
unsigned char
), если вы не создадитеtypedef
первый.Да, и помните, если этот код на самом деле нигде не используется, то вы даже не знаете, правильно ли он синтаксически.
РЕДАКТИРОВАТЬ: Еще одна вещь: вам нужно вручную управлять созданием кода. Если ваш "шаблонный" код - это не все встроенные функции, вам нужно будет ввести некоторый контроль, чтобы гарантировать, что все будет создано только один раз, чтобы ваш компоновщик не выплюнул кучу "нескольких экземпляров Foo". .
Для этого вам нужно будет поместить невстроенный материал в раздел «реализация» вашего файла заголовка:
#ifdef implementation_##TYPE /* Non-inlines, "static members", global definitions, etc. go here. */ #endif
И затем, в одном месте всего вашего кода для каждого варианта шаблона , вы должны:
#define TYPE Packet #define implementation_Packet #include "Queue.h" #undef TYPE
Кроме того, этот раздел реализации должен находиться за пределами стандартной
#ifndef
/#define
/#endif
литании, потому что вы можете включить файл заголовка шаблона в другой файл заголовка, но после этого потребуется создать его экземпляр в.c
файле.Да, быстро становится уродливо. Вот почему большинство программистов на C даже не пробуют.
RAII.
Особенно в функциях с несколькими точками возврата, например, когда не нужно забывать отпускать мьютекс в каждой точке возврата.
Что ж, забудьте свой красивый код и привыкните к тому, что все ваши точки возврата (кроме конца функции) будут
goto
s:TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { TYPE * result; Mutex_Lock(this->lock); if(this->head == this->tail) { result = 0; goto Queue_##TYPE##_Top_exit:; } /* Figure out `result` for real, then fall through to... */ Queue_##TYPE##_Top_exit: Mutex_Lock(this->lock); return result; }
Деструкторы в целом.
Т.е. вы пишете d'tor один раз для MyClass, тогда, если экземпляр MyClass является членом MyOtherClass, MyOtherClass не должен явно деинициализировать экземпляр MyClass - его d'tor вызывается автоматически.
Создание объекта должно выполняться точно так же.
Пространства имен.
На самом деле это просто исправить: просто прикрепите префикс к каждому символу. Это основная причина раздувания исходного кода, о котором я говорил ранее (поскольку классы являются неявными пространствами имен). Ребята из Си жили этим, ну, вечно, и, вероятно, не поймут, в чем дело.
YMMV
источник
AT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
BOARD_OSCOUNT
(каково значение тайм-аута для ожидания переключения часов; ясно, а?) На самом деле#define
inboard.h
. В той же самой функции есть много кода цикла с копированием и вставкой, который должен был быть преобразован в двухстрочный#define
(и, когда я это сделал, он сохранил несколько байтов кода и сделал функция более удобочитаема, делая наборы регистров и циклы вращения более четкими). Одна из главных причин использования C заключается в том, что он позволяет вам все контролировать и оптимизировать, но большая часть кода, который я видел, не беспокоит.Я перешел с C ++ на C по другой причине (какая-то аллергическая реакция;), и есть только несколько вещей, которые мне не хватает, и некоторые вещи, которые я получил. Если вы придерживаетесь C99, то, если можете, есть конструкции, которые позволят вам программировать довольно красиво и безопасно, в частности
for
-scope переменная может помочь вам в управлении ресурсами с привязкой к области видимости , в частности, для обеспеченияunlock
мьютексов илиfree
массивов, даже при предварительном возврате функции__VA_ARGS__
макросы могут использоваться, чтобы иметь аргументы по умолчанию для функций и выполнять развертывание кодаinline
функции и макросы, которые хорошо сочетаются для замены (вроде) перегруженных функцийисточник
for
прицелов, вы попадете на P99, где вы также можете посмотреть примеры и описания других частей.void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }
и инициализацию структуры по именам полей (напримерstruct Foo bar = { .field1 = 5, .field2 = 10 };
), последнее, что я хотел бы видеть в C ++, особенно с объектами, не относящимися к POD (напримерUART uart[2] = { UART(0x378), UART(0x278) };
) .Для C. Ничего подобного STL не существует.
Существуют библиотеки, которые предоставляют аналогичные функции, но они больше не встроены.
Думаю, это будет одна из моих самых больших проблем ... Зная, с помощью какого инструмента я могу решить проблему, но не имея инструментов, доступных на языке, который я должен использовать.
источник
Разница между C и C ++ заключается в предсказуемости поведения кода.
Легче предсказать с большой точностью, что будет делать ваш код на C, в C ++ может стать немного сложнее придумать точный прогноз.
Предсказуемость в C дает вам лучший контроль над тем, что делает ваш код, но это также означает, что вам нужно делать больше вещей.
В C ++ вы можете писать меньше кода, чтобы делать то же самое, но (по крайней мере, для меня) у меня иногда возникают проблемы с пониманием того, как объектный код размещен в памяти и его ожидаемое поведение.
источник
-s
флаг,gcc
чтобы получить дамп сборки, найти интересующую функцию и начать чтение. Это отличный способ изучить особенности любого компилируемого языка.В моей работе - которая, кстати, встроена - я постоянно переключаюсь между C и C ++.
Когда я на C, я скучаю по C ++:
шаблоны (включая, помимо прочего, контейнеры STL). Я использую их для таких вещей, как специальные счетчики, буферные пулы и т. Д. (Создал свою собственную библиотеку шаблонов классов и шаблонов функций, которые я использую в различных встроенных проектах)
очень мощная стандартная библиотека
деструкторы, которые, конечно, делают возможным использование RAII (мьютексы, отключение прерываний, трассировка и т. д.)
спецификаторы доступа, чтобы лучше контролировать, кто может использовать (не видеть) что
Я использую наследование в более крупных проектах, и встроенная поддержка C ++ для него намного чище и приятнее, чем "хитрость" C по встраиванию базового класса в качестве первого члена (не говоря уже об автоматическом вызове конструкторов, списков инициализации и т. Д.) ), но перечисленных выше пунктов я скучаю больше всего.
Кроме того, вероятно, только около трети встроенных проектов C ++, над которыми я работаю, используют исключения, поэтому я привык жить без них, поэтому я не слишком по ним скучаю, когда возвращаюсь на C.
С другой стороны, когда я возвращаюсь к проекту C со значительным количеством разработчиков, появляются целые классы проблем C ++, которые я привык объяснять людям, которые уходят. В основном проблемы из-за сложности C ++ и людей, которые думают, что знают, что происходит, но на самом деле они находятся на этапе «C с классами» кривой уверенности C ++ .
Если бы у меня был выбор, я бы предпочел использовать C ++ в проекте, но только если команда хорошо разбирается в языке. Также, конечно, если предположить, что это не проект 8K μC, где я в любом случае пишу «C».
источник
Пара наблюдений
источник
Практически те же причины, по которым я использую C ++ или смесь C / C ++, а не чистый C. Я могу жить без пространств имен, но я использую их все время, если это позволяет стандарт кода. Причина в том, что вы можете писать гораздо более компактный код на C ++. Это очень полезно для меня, я пишу серверы на C ++, которые время от времени дают сбой. В этом случае очень помогает, если код, на который вы смотрите, короткий и содержательный. Например, рассмотрим следующий код:
uint32_t ScoreList::FindHighScore( uint32_t p_PlayerId) { MutexLock lock(m_Lock); uint32_t highScore = 0; for(int i = 0; i < m_Players.Size(); i++) { Player& player = m_Players[i]; if(player.m_Score > highScore) highScore = player.m_Score; } return highScore; }
В C это выглядит так:
uint32_t ScoreList_getHighScore( ScoreList* p_ScoreList) { uint32_t highScore = 0; Mutex_Lock(p_ScoreList->m_Lock); for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++) { Player* player = p_ScoreList->m_Players[i]; if(player->m_Score > highScore) highScore = player->m_Score; } Mutex_UnLock(p_ScoreList->m_Lock); return highScore; }
Никакой разницы. Еще одна строка кода, но она складывается. Обычно вы изо всех сил стараетесь поддерживать его в чистоте и поджарости, но иногда вам приходится делать что-то более сложное. И в таких ситуациях вы цените количество строк. Еще одна строка - это еще одна вещь, на которую следует обратить внимание, когда вы пытаетесь выяснить, почему ваша широковещательная сеть внезапно перестает доставлять сообщения.
В любом случае, я считаю, что C ++ позволяет мне безопасно выполнять более сложные задачи.
источник
Я думаю, что основная проблема, почему C ++ труднее принять во встраиваемой среде, заключается в отсутствии инженеров, которые понимают, как правильно использовать C ++.
Да, те же рассуждения можно применить и к C, но, к счастью, в C не так много ловушек, которые могут привести к выстрелу себе в ногу. С ++, с другой стороны, вам нужно знать, когда не следует использовать определенные функции С ++.
В целом, мне нравится C ++. Я использую это на уровне сервисов O / S, драйвере, коде управления и т. Д. Но если ваша команда не имеет достаточного опыта с этим, это будет трудная задача.
У меня был опыт работы с обоими. Когда остальная часть команды не была готова к этому, это была полная катастрофа. С другой стороны, это был хороший опыт.
источник
да! Я испытал оба этих языка и обнаружил, что C ++ более дружелюбный. Это облегчает с большим количеством функций. Лучше сказать, что C ++ является надмножеством языка C, поскольку он предоставляет дополнительные функции, такие как полиморфизм, интеританс, перегрузка операторов и функций, определяемые пользователем типы данных, которые на самом деле не поддерживаются в C. Тысячи строк кода сокращаются до нескольких строк с помощью помощь объектно-ориентированного программирования - основная причина перехода с C на C ++.
источник