Лучше использовать директиву препроцессора или оператор if (constant)?

10

Допустим, у нас есть кодовая база, которая используется для многих разных клиентов, и у нас есть некоторый код, который важен только для клиентов типа X. Лучше ли использовать директивы препроцессора, чтобы включать этот код только в клиент типа X, или использовать если заявления? Чтобы быть понятнее:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

или

if(TYPE_X_COSTUMER) {
    // do some things
}

Аргументы, о которых я могу думать:

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

Что предпочтительнее - когда речь идет о кодовой базе для разных клиентов?

MByD
источник
3
Каковы шансы, что вы отправите сборку, не созданную с помощью оптимизирующего компилятора? И если это произойдет, будет ли иметь значение влияние (особенно если рассматривать его в контексте всех других оптимизаций, которые вы пропустите)?
@delnan - код используется для разных платформ с разными компиляторами. А что касается воздействия - возможно, в некоторых случаях.
MByD
Вам сказали что-то предпочесть? Это все равно что заставлять кого-то есть еду из KFC, когда он не любит курицу.
вправо
@ WTP - нет, не было, мне интересно узнать больше, поэтому я буду принимать лучшие решения в будущем.
MByD
Во втором примере TYPE_X_CUSTOMERвсе еще есть макрос препроцессора?
детально

Ответы:

5

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

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

Тамас Селеи
источник
На самом деле, это является для встраиваемых систем
MByD
Проблема в том, что встроенные системы обычно используют какой-то древний компилятор (например, gcc 2.95).
BЈовић
@VJo - GCC - счастливый случай :)
MByD
Попробуйте это тогда. Если он содержит неиспользуемый код и это значительный размер, тогда перейдите к определениям.
Тамас Селеи
Если вы хотите установить его через командную строку компилятора, я бы все равно использовал константу в самом коде и только # определял значение, присвоенное этой константе.
CodesInChaos
12

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

И препроцессор, и константа имеют свои собственные места соответствующего использования.

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

  1. Многоплатформенный код:
    например, когда код компилируется на разных платформах, когда код зависит от конкретных номеров версий ОС (или даже от версии компилятора - хотя это очень редко). Например, когда вы имеете дело с непоследовательными бинарными аналогами кода - они должны быть отделены от препроцессоров, а не констант. Или, если вы компилируете код для Windows, а Linux и некоторые системные вызовы очень разные.

  2. Экспериментальные исправления.
    Другим случаем, когда это оправдано, является наличие экспериментального кода, который является рискованным, или определенных основных модулей, которые необходимо исключить, которые будут иметь существенные различия в ссылках или производительности. Причина, по которой можно захотеть отключить код с помощью препроцессора, а не скрывать его под if (), заключается в том, что мы не можем быть уверены в ошибках, внесенных этим конкретным набором изменений, и работаем на экспериментальной основе. Если это не удается, мы должны сделать только одно: отключить этот код в производственной среде, кроме перезаписи. Некоторое время идеально использовать #if 0 для комментирования всего кода.

  3. Работа с зависимостями: еще
    одна причина, по которой вам может понадобиться сгенерировать. Например, если вы не хотите поддерживать изображения JPEG, вы можете помочь избавиться от компиляции этого модуля / заглушки, и в конечном итоге библиотека не будет (статически или динамически) связываться с этим модуль. Иногда пакеты запускаются ./configureдля определения доступности такой зависимости, и если библиотеки отсутствуют (или пользователь не хочет их включать), такая функциональность отключается автоматически без привязки к этой библиотеке. Здесь всегда полезно, если эти директивы генерируются автоматически.

  4. Лицензирование.
    Одним из очень интересных примеров директивы препроцессора является ffmpeg . Он имеет кодеки, которые могут потенциально нарушать патенты при его использовании. Если вы загрузите исходный код и скомпилируете его для установки, он спросит вас, хотите ли вы или удалите такие вещи. Сохраняя коды , спрятанные под некоторыми , если условия по- прежнему могут посадить Вас в суде!

  5. Код копировать-вставить:
    ака макросы. Это не совет по чрезмерному использованию макросов - просто у макросов гораздо более эффективный способ применения эквивалента после копирования . Но используйте это с большой осторожностью; и используйте его, если знаете, что делаете. Константы, конечно, не могут этого сделать. Но можно также использовать inlineфункции, если это легко сделать.

Так когда вы используете константы?
Почти везде.

  1. Более аккуратный поток кода:
    в общем, когда вы используете константы, он почти неотличим от обычных переменных и, следовательно, лучше читаемый код. Если вы пишете подпрограмму, которая состоит из 75 строк, используйте 3 или 4 строки после каждых 10 строк, а #ifdef ОЧЕНЬ не сможет прочитать . Вероятно, с заданной первичной константой, управляемой #ifdef, и использующей ее в любом естественном потоке.

  2. Код с хорошим отступом : все директивы препроцессора, никогда не работают хорошо с другим кодом с хорошим отступом . Даже если ваш компилятор допускает отступ #def, препроцессор Pre-ANSI C не допускал пробела между началом строки и символом "#"; ведущий "#" должен был всегда находиться в первом столбце.

  3. Конфигурация:
    Еще одна причина, по которой константы / или переменные имеют смысл, заключается в том, что они могут легко эволюционировать от связи либо с глобальными, либо в будущем могут быть расширены для получения из файлов конфигурации.

И последнее:
никогда не ИСПОЛЬЗУЙТЕ директивы препроцессора #ifdefдля #endif пересечения области или { ... }. то есть начало #ifdefили конец #endifпо разные стороны { ... }. Это очень плохо; это может сбивать с толку, это может быть иногда опасно.

Это, конечно, не исчерпывающий список, но он показывает серьезную разницу в том, какой метод лучше использовать. На самом деле дело не в том, что лучше , а в том, что более естественно использовать в данном контексте.

Дипан Мехта
источник
Спасибо за длинный и подробный ответ. Я был знаком с большинством вещей, которые вы упомянули, и они были опущены в вопросе, поскольку вопрос заключается не в том, использовать ли возможности препроцессора вообще, а в конкретном типе использования. Но я думаю, что основная мысль, которую вы сделали, верна.
MByD
1
«Никогда не ИСПОЛЬЗУЙТЕ директивы препроцессора #ifdef - #endif, пересекающие область действия или {...}», это зависит от IDE. Если IDE распознает неактивные блоки препроцессора и сворачивает их или показывает другим цветом, это не смущает.
Abyx
@Abyx это правда, что IDE может помочь. Однако во многих случаях, когда люди разрабатывают под платформой * nix (linux и т. Д.), Выбор редакторов и т. Д. Велик (VI, emacs и т. Д.). Так что кто-то другой может использовать редактор, отличный от вашего.
Дипан Мехта
4

У ваших клиентов есть доступ к вашему коду? Если да, то препроцессор может быть лучшим вариантом. У нас есть что-то похожее, и мы используем флаги времени компиляции для различных клиентов (или специфических особенностей клиента). Затем скрипт может извлечь код, специфичный для клиента, и мы отправим этот код. Клиенты не знают о других клиентах или других специфических особенностях клиента.

Адитья Сегал
источник
3

Несколько дополнительных моментов, достойных упоминания:

  1. Компилятор может обрабатывать некоторые виды константных выражений, которые препроцессор не может. Например, препроцессор, как правило, не будет иметь возможности оценивать sizeof()не примитивные типы. Это может заставить использование if()в некоторых случаях.

  2. Компилятор будет игнорировать большинство вопросов синтаксиса в коде , который пропускается в #if(некоторые вопросы , связанные с препроцессором все еще могут вызвать проблемы) , но настаивают на синтаксическую корректность коды пропуска if(). Таким образом, если код, который пропущен для некоторых сборок, но не для других, становится недействительным вследствие изменений в другом месте файла (например, переименованных идентификаторов), он обычно будет работать на всех сборках, если он отключен, if()но не отключен, если он отключен #if. В зависимости от того, почему код пропускается, это может быть или не быть хорошей вещью.

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

  4. Макросы могут использовать if()тесты внутри них, но, увы, у препроцессора нет механизма для выполнения какой-либо условной логики в макросе [обратите внимание, что для использования внутри макросов ? :оператор часто может быть лучше if, но применяются те же принципы].

  5. #ifДиректива может использоваться , чтобы контролировать то , что макросы получить определены.

Я думаю, что if()в большинстве случаев чище, за исключением тех, которые связаны с принятием решений на основе того, какой компилятор используется или какие макросы должны быть определены; #ifоднако некоторые из перечисленных выше проблем могут потребовать использования даже в тех случаях, когда ifони кажутся более чистыми.

Supercat
источник
1

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

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

Микро
источник
Это отличная база кода, и определения действительно находятся в отдельных заголовках.
MByD