Чи краще використовувати директиву препроцесора або якщо (константа) твердження?


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
}

Я можу думати про такі аргументи:

  • Директива пропроцесора призводить до меншого сліду коду та меншої кількості гілок (для неоптимізуючих компіляторів)
  • Якщо висловлювання призводять до коду, який завжди компілюється, наприклад, якщо хтось допустить помилку, яка завдасть шкоди нерелевантному коду проекту, над яким він працює, помилка все одно з’явиться, і він не пошкодить базу коду. Інакше про корупцію він не буде відомий.
  • Мені завжди говорили, що я віддаю перевагу використанню процесора перед використанням препроцесора (Якщо це взагалі аргумент ...)

Що є кращим - якщо говорити про кодову базу для багатьох клієнтів?


3
Які шанси ви будете відправляти на збірку, не створену за допомогою компілятора оптимізації? І якщо це станеться, чи матиме значення вплив (особливо якщо поставити його в контекст усіх інших оптимізацій, які ви пропустите)?

@delnan - код використовується для багатьох різних платформ з багатьма різними компіляторами. А щодо впливу - це може бути в певних випадках.
MByD

Вам сказали, що віддаєте перевагу? Це як змусити когось їсти їжу з KFC, поки він не любить курку.
праворуч

@WTP - ні, я не був, мені цікаво знати більше, тому я б приймав кращі рішення в майбутньому.
MByD

У вашому другому прикладі TYPE_X_CUSTOMERвсе ще є макрос препроцесора?
detly

Відповіді:


5

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

Крім усього іншого, загалом краще уникати макросів. Вони не поважають жодного масштабування, і це може спричинити проблеми. Лише дуже тупі компілятори не можуть оптимізувати умову на основі константи часу компіляції. Я не знаю, чи це турбує ваш продукт (наприклад, може бути важливо зберегти код невеликим на вбудованих платформах).


На насправді, це є для вбудованих систем
MByD

Проблема полягає в тому, що вбудовані системи зазвичай використовують стародавній компілятор (наприклад, gcc 2.95).
BЈович

@VJo - GCC - щасливий випадок :)
MByD

Спробуйте потім. Якщо він включає невикористаний код і має значні розміри, тоді перейдіть до визначених.
Tamás Szelei

Якщо ви хочете мати можливість встановити його за допомогою командного рядка компілятора, я все одно використовую константу в самому коді і лише #define значення, присвоєне цій константі.
CodesInChaos

12

Що стосується багатьох питань, то відповідь на це питання - це залежить . Замість того, щоб сказати, що краще, я скоріше наводив приклади та цілі, коли один кращий за інших.

Як препроцесор, так і константа мають свої місця відповідного використання.

У випадку попереднього процесора код видаляється до часу компіляції. Отже, він найкраще підходить для ситуацій, коли очікується, що код не буде складений . Це може вплинути на структуру модуля, залежності, і це може дозволити вибір найкращих сегментів коду для аспектів продуктивності. У наступних випадках потрібно розділити код лише за допомогою препроцесора.

  1. Багатоплатформний код:
    Наприклад, коли код компілюється на різних платформах, коли код залежить від конкретних номерів версій ОС (або навіть версії компілятора - хоча це дуже рідко). Наприклад, коли ви маєте справу з малопомітними аналогами великих ендієнів коду - вони повинні бути відокремлені з препроцесорами, а не константами. Або якщо ви збираєте код для Windows, а також Linux, а певні системні дзвінки дуже різні.

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

  3. Справа із залежностями:
    Ще одна причина, з якої ви можете сформувати Наприклад, якщо ви не хочете підтримувати зображення JPEG, ви можете допомогти позбутися від компіляції цього модуля / заглушки, і в кінцевому підсумку бібліотека не буде (статично чи динамічно) посилатися на це модуль. Іноді пакети запускаються ./configureдля ідентифікації доступності такої залежності, і якщо бібліотеки відсутні (або користувач не хоче включати), така функціональність відключається автоматично, не пов'язуючи її з цією бібліотекою. Тут завжди вигідно, якщо ці директиви генеруються автоматично.

  4. Ліцензування:
    Одним з дуже цікавих прикладів директиви препроцесора є ffmpeg . Він має кодеки, які потенційно можуть порушувати патенти при його використанні. Якщо ви завантажите джерело і компілюєте для встановлення, він запитає, чи хочете ви або не тримаєте подалі такі речі. Зберігання кодів прихованих за певних умов, якщо умови все-таки можуть стати вами в суді!

  5. Копіювати-вставити код:
    макроси Aka. Це не порада надмірно використовувати макроси - просто такі макроси мають набагато більш потужний спосіб застосувати еквівалент копіювання минулого . Але використовуйте його з великою обережністю; і використовуйте його, якщо ви знаєте, що ви робите. Константи, звичайно, не можуть цього зробити. Але можна використовувати і inlineфункції, якщо це легко зробити.

То коли ви використовуєте константи?
Майже скрізь.

  1. Охайний потік коду:
    загалом, коли ви використовуєте константи, він майже не відрізняється від звичайних змінних, а значить, краще читати код. Якщо ви пишете процедуру, що становить 75 рядків, після 3-х рядків через 3 і 4 рядки з #ifdef ДУЖЕ не вдається прочитати . Ймовірно, дається первинна константа, якою керує #ifdef, і використовувати її в природному потоці скрізь.

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

  3. Конфігурація:
    Ще одна причина, через яку константи / або змінні мають сенс, полягає в тому, що вони можуть легко розвиватися від того, щоб бути пов'язаними з глобальними глобалами, або в майбутньому можуть бути розширені, щоб отримати від файлів конфігурації.

Одна остання річ:
ніколи не використовуйте директиви препроцесора #ifdefдля #endif перетину сфери або { ... }. тобто початок #ifdefабо кінець з #endifрізних сторін { ... }. Це надзвичайно погано; це може заплутати, може бути десь небезпечно.

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


Дякую за довгу та детальну відповідь. Мені було відомо про більшість згаданих вами речей, і вони були виключені з запитання, оскільки питання полягає не в тому, чи взагалі використовувати можливості препроцесора, а про конкретний тип використання. Але я думаю, що основний пункт, який ви зробили, - це правда.
MByD

1
"Ніколи не використовуйте директиви препроцесора #ifdef до #endif, що перетинають область, або {...}", це залежить від IDE. Якщо IDE розпізнає неактивні блоки препроцесора та згортає їх або показує з іншим кольором, це не заплутано.
Абікс

@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вони здадуться більш чистими.


1

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

Якщо ваш проект невеликий і майже не має великого плану на майбутнє, я думаю, що обидва варіанти пропонують однакове співвідношення плюсів і мінусів, але якщо ваш проект буде величезним, розгляньте щось інше, як написання окремого файлу заголовка, якщо ви все ще хочете використовувати директиви препроцесора, але майте на увазі, що такий тип підходу зазвичай робить код менш читабельним і будьте впевнені, що назва цих констант означає щось для ділової логіки самої програми.


Це чудова база коду, і дефініції справді є в окремих заголовках.
MByD
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.