Переход с C ++ на C

83

После нескольких лет кодирования на C ++ мне недавно предложили работу кодирования на C во встроенной области.

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

  • Общие, типобезопасные структуры данных (с использованием шаблонов).
  • RAII. Особенно в функциях с несколькими точками возврата, например, когда не нужно забывать отпускать мьютекс в каждой точке возврата.
  • Деструкторы в целом. Т.е. вы пишете d'tor один раз для MyClass, тогда, если экземпляр MyClass является членом MyOtherClass, MyOtherClass не должен явно деинициализировать экземпляр MyClass - его d'tor вызывается автоматически.
  • Пространства имен.

Каков ваш опыт перехода с C ++ на C?
Какие замены C вы нашли для своих любимых функций / идиом C ++? Обнаружили ли вы какие-либо функции C, которые вам хотелось бы иметь в C ++?

Джордж
источник
12
Вероятно, это должна быть вики сообщества, если вы просто просите опыта, а не совета.
Питер Александр
6
Вы можете быть заинтересованы в Prog.SE .
11
@Peter: OP больше не может задавать вопросы CW, и для этого требовалось больше репутации, чем он имел, когда это было еще возможно. Если вы думаете, что вопрос следует сделать вики сообщества по какой-либо другой причине, кроме того, чтобы позволить большему количеству пользователей редактировать сообщения, принадлежащие сообществу, то вы действительно хотите закрыть вопрос.
4
Разве этот вопрос не подошел бы больше для сайта programmers.se? Поскольку это определенно «настоящий» вопрос, я говорю, что мы снова открываем его и голосуем за его перемещение. А это невозможно. ХОРОШО.
Лассе В. Карлсен
21
Перемещения не произойдет, пока прога SE не выйдет из бета-версии, и в любом случае я думаю, что такой подход к QA убивает мозги. Это фрагментирует сообщество, раздражает пользователей, дублирует вопросы и ответы. Это создает беспорядок из неорганизованной информации, которая раньше была доступна и по которой можно было перемещаться на единственном сайте «программиста». Вдобавок именно такие вопросы, как этот, с огромным количеством просмотров и невероятным количеством голосов, заставляют меня злиться между 5 whack-a-close и сообществом в целом.
Стефано Борини

Ответы:

68

Работая над встроенным проектом, я однажды попробовал работать на всем 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.

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

Что ж, забудьте свой красивый код и привыкните к тому, что все ваши точки возврата (кроме конца функции) будут gotos:

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

Майк Дезимоун
источник
60
Конечно, вы ненавидите C, если вы пытаетесь заставить его быть C ++. Я сомневаюсь, что C ++ выглядел бы великолепно, если бы вы также попытались навязать ему функции из $ more_expressive_language. Не критика вашего поста, а просто наблюдение :-)
Относительно техники goto-вместо-RAII: разве это не кошмар обслуживания? т.е. всякий раз, когда вы добавляете путь кода, который требует очистки, или даже просто меняете порядок вещей внутри функции, вы должны не забыть перейти к меткам goto в конце и также изменить их. Я хотел бы увидеть технику, которая каким-то образом регистрирует код очистки рядом с тем, что нужно очистить.
Джордж
2
@george: Ненавижу это говорить, но большинство встроенных кодов C, которые я видел, довольно плохи по стандартам C. Например, я сейчас работаю с Atmel at91lib, и он требует, чтобы вы написали файл «board.h», который большая часть их кода использует как зависимость. (Для их демонстрационной платы этот заголовок состоит из 792 строк.) Кроме того, функция «LowLevelInit ()», которую вы должны настроить для своей платы, почти полностью представляет собой регистрацию доступа со строками вродеAT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
Mike DeSimone
1
О, и ничего там не говорит вам о том, что BOARD_OSCOUNT(каково значение тайм-аута для ожидания переключения часов; ясно, а?) На самом деле #definein board.h. В той же самой функции есть много кода цикла с копированием и вставкой, который должен был быть преобразован в двухстрочный #define(и, когда я это сделал, он сохранил несколько байтов кода и сделал функция более удобочитаема, делая наборы регистров и циклы вращения более четкими). Одна из главных причин использования C заключается в том, что он позволяет вам все контролировать и оптимизировать, но большая часть кода, который я видел, не беспокоит.
Майк Дезимоун
5
Согласитесь с @Mads. Нет причин проходить через все это ради функций, которые вам действительно не нужны. Мне нравится стиль, похожий на библиотеку GTK. Определите свои «классы» как структуры, а затем создайте согласованные методы, такие как my_class_new (), а затем передайте их в «методы»: my_class_do_this (my_class_instance)
Макс.
17

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

  • назначенные инициализаторы (в конечном итоге в сочетании с макросами) делают инициализацию простых классов такой же простой, как и конструкторы
  • составные литералы для временных переменных
  • for-scope переменная может помочь вам в управлении ресурсами с привязкой к области видимости , в частности, для обеспечения unlockмьютексов или freeмассивов, даже при предварительном возврате функции
  • __VA_ARGS__ макросы могут использоваться, чтобы иметь аргументы по умолчанию для функций и выполнять развертывание кода
  • inline функции и макросы, которые хорошо сочетаются для замены (вроде) перегруженных функций
Йенс Густедт
источник
2
@Mike: для какой именно части? Если вы перейдете по ссылке, которую я дал для forприцелов, вы попадете на P99, где вы также можете посмотреть примеры и описания других частей.
Йенс Густедт
1
@ Майк: Ну вот.
Джордж
@george: Спасибо! @Jens: Примеры остальных четырех. Я отстал от своего C; последнее, что я слышал, они добавили автоматически выделяемые (т.е. стековые) массивы (например, размер стека) 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) };) .
Майк Дезимоун
@Mike: да, есть массивы переменной длины (VLA), но они могут быть немного опасны в использовании из-за потенциального переполнения стека. Второй, который вы описываете, - это именно «назначенный инициализатор», так что вы можете перейти к вашему собственному примеру ;-) Для остальных вы найдете информацию по ссылке P99 выше, если вы нажмете на «связанные страницы».
Йенс Густедт
8

Для C. Ничего подобного STL не существует.
Существуют библиотеки, которые предоставляют аналогичные функции, но они больше не встроены.

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

MOnsDaR
источник
это правда. может ли кто-нибудь уточнить, какие библиотеки контейнерных классов следует использовать для C? Или ответ «напиши сам»?
Sandeep
@Sandeep: Во-первых, этот ответ верен только в том, что контейнеры не входят в стандартную библиотеку. За исключением отсутствия эквивалента STL (лучшая часть C ++), стандартная библиотека C намного превосходит ее. POSIX содержит tsearch, lsearch, hsearch и bsearch в дополнение к qsort, который находится в libc. Glib - это окончательный "Boost" языка C, посмотрите, он упакован вкусностями (включая контейнеры). library.gnome.org/devel/glib/stable . Glib также интегрируется с Gtk +, комбинацией, которая превосходит Boost и Qt. Существует также libapr, популярный для таких вещей xplatform, как Subversion и Apache.
Мэтт Джойнер
Я не могу найти никаких библиотек c, которые могли бы конкурировать с stl, их труднее использовать, сложнее поддерживать, производительность не является конкурентом stl, когда они хотят сохранить c libs такими же общими, как stl. ограничение c и причины, по которым у нас не может быть чего-то вроде stl в библиотеке c, потому что c просто не имеет возможности разработать что-то вроде stl.
StereoMatching
8

Разница между C и C ++ заключается в предсказуемости поведения кода.

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

Предсказуемость в C дает вам лучший контроль над тем, что делает ваш код, но это также означает, что вам нужно делать больше вещей.

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

ds
источник
4
Всякий раз, когда я беспокоюсь о том, что на самом деле делает код, я добавляю -sфлаг, gccчтобы получить дамп сборки, найти интересующую функцию и начать чтение. Это отличный способ изучить особенности любого компилируемого языка.
Майк Дезимоун
2
Это также пустая трата времени, поскольку сборка, созданная на C ++, будет похожа на чтение Perl. Браво, все равно ищи.
Мэтт Джойнер
7

В моей работе - которая, кстати, встроена - я постоянно переключаюсь между C и C ++.

Когда я на C, я скучаю по C ++:

  • шаблоны (включая, помимо прочего, контейнеры STL). Я использую их для таких вещей, как специальные счетчики, буферные пулы и т. Д. (Создал свою собственную библиотеку шаблонов классов и шаблонов функций, которые я использую в различных встроенных проектах)

  • очень мощная стандартная библиотека

  • деструкторы, которые, конечно, делают возможным использование RAII (мьютексы, отключение прерываний, трассировка и т. д.)

  • спецификаторы доступа, чтобы лучше контролировать, кто может использовать (не видеть) что

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

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

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

Если бы у меня был выбор, я бы предпочел использовать C ++ в проекте, но только если команда хорошо разбирается в языке. Также, конечно, если предположить, что это не проект 8K μC, где я в любом случае пишу «C».

Дэн
источник
2
Эта «кривая уверенности в C ++» меня немного беспокоит. То, как он написан, и комментарии подразумевают, что C ++ безнадежен, безнадежно или что-то еще. Я что-то упускаю?
Майк Дезимоун
Сделайте решительный шаг, увидимся через несколько лет. Большинство хороших программистов выходят с другой стороны с кислым вкусом.
Мэтт Джойнер
3

Пара наблюдений

  • Если вы не планируете использовать свой компилятор C ++ для создания своего C (что возможно, если вы придерживаетесь четко определенного подмножества C ++), вы скоро обнаружите то, что ваш компилятор допускает в C, что было бы ошибкой компиляции в C ++.
  • Больше никаких загадочных ошибок шаблона (ура!)
  • Нет (поддерживается язык) объектно-ориентированного программирования
хафез
источник
C не поддерживает шаблон, это не означает, что нам не нужны «общие парадигмы», в C вы должны использовать void * и макрос для имитации шаблона, если вам нужны «общие парадигмы». Void * не является типобезопасным, ошибки макроса также довольно дрянной, не лучше, чем шаблон. Шаблон намного легче читать и поддерживать, чем макрос, плюс безопасный тип.
StereoMatching
2

Практически те же причины, по которым я использую 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 вы не можете сделать это «for (int i = 0».
Виктор
6
Виктор, это действительно c99 (или компиляция c с помощью компилятора c ++ для некоторых проблем с набором текста).
Роман А. Тайчер
Я считаю, что не могу доверять безопасности. Итак, ваш мьютекс ограничен. Теперь вы не знаете, почему его разблокируют, если исключение «блуждает». Вы даже не знаете, когда он будет разблокирован, любая часть вашего кода может решить, что с него хватит, и выбросить. Эти дополнительные неявные «меры безопасности» могут маскировать ошибки.
Мэтт Джойнер
Мэтт, мы знаем, почему он был разблокирован. В обычном случае, когда программа достигает конца области видимости, мьютекс разблокируется, нам не нужно разблокировать его вручную кодами, это кошмар обслуживания. Если возникает исключение, мьютекс будет разблокирован, и мы сможем перехватить исключение и прочитать сообщение об ошибке. Сообщение об ошибке достаточно хорошее или нет, будет зависеть от того, как вы играете с исключением.
StereoMatching
0

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

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

В целом, мне нравится C ++. Я использую это на уровне сервисов O / S, драйвере, коде управления и т. Д. Но если ваша команда не имеет достаточного опыта с этим, это будет трудная задача.

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

KOkon
источник
0

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

кайнат лиакат
источник
C ++ на самом деле не является надмножеством C; очень легко написать код C, который не может быть скомпилирован на компиляторе C ++. С другой стороны, Objective-C был (является?) Строгим надмножеством C.
ad absurdum
@exnihilo Расширенное соглашение здесь должно определить, что C ++ имеет больше возможностей. Это также улучшает синтаксис и семантику и снижает вероятность ошибки. Есть код, который не компилировался на C ++, но может компилироваться на C, например const int a; Это приведет к ошибке в C ++, так как необходимо инициализировать константу во время объявления, в то время как в C концепция будет компилироваться. Таким образом, надмножество - это не жесткое и быстрое правило, как в математике (A⊂B), а скорее приближение того, что C ++ предоставляет дополнительные функции, такие как объектно-ориентированная концепция.
kaynat liaqat