C - навіщо використовувати явно безглузді інструкції do-while і if-else в макросах c-preprocessor,

do. while і if. else. щоб зробити так, щоб крапка з комою після вашого макросу завжди означала одне і те ж. Припустимо, у вас було щось подібне до вашого другого макросу.

Тепер, якщо ви повинні використовувати BAR (X); в вираженні if. else. де тіла оператора if були загорнуті в фігурні дужки, ви отримаєте поганий сюрприз.

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

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

Вищезазначена версія bar BAR розширює наведений вище код до наступного, що є синтаксично правильним.

Вам не потрібно використовувати do. while. ви могли б зварити щось з if. else. але якщо if. else розшириться всередині if. else це призведе до «зависання» », що могло б ще більше ускладнити виявлення існуючої проблеми з іншою проблемою, як в наступному коді.

Справа в тому, щоб використовувати крапку з комою в контекстах, де помилкова точка з коми помилкова. Звичайно, в цей момент можна (і, ймовірно, слід) стверджувати, що було б краще оголосити BAR як фактичну функцію, а не макрос.

Таким чином, do. while потрібно, щоб обійти недоліки препроцесора C. Коли ці керівництва по стилю C кажуть вам про те, щоб прибрати препроцесор C, це те, про що вони турбуються.

Є три хороших «підказки», щоб домогтися успіху в цьому:

Допоможіть макросу поводитися як справжній код

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

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

Тому у нас повинен бути макрос, який потребує напівколонії.

Проведіть дійсний код

Як показано у відповіді jfm3, іноді макрос містить більше однієї інструкції. І якщо макрос використовується всередині оператора if, це буде проблематично:

Цей макрос можна розгорнути як:

Функція g буде виконуватися незалежно від значення bIsOk.

Це означає, що нам потрібно додати область макросу:

Проведіть дійсний код 2

Якщо макрос є щось на зразок:

У нас може бути інша проблема в наступному коді:

Оскільки він буде розширюватися в такий спосіб:

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

Код поводиться коректно знову.

Поєднання ефектів з повзунком + масштабами?

Існує одна ідіома C / C ++, яка виробляє цей ефект: цикл do / while:

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

Компілятор C ++ оптимізує цикл do / while, оскільки факт його пост-стану є хибним, відомо під час компіляції. Це означає, що макрос на кшталт:

буде правильно розширюватися, оскільки

і потім компілюється і оптимізується, як

@ Jfm3 - У вас є хороший відповідь на питання. Ви також можете додати, що ідіома макросу також запобігає можливому більш небезпечне (бо немає помилки) ненавмисне поведінку з простими операціями «if»:

який синтаксично коректний, тому немає помилки компілятора, але має, ймовірно, ненавмисне наслідок того, що g () завжди буде викликатися.

Вищенаведені відповіді пояснюють сенс цих конструкцій, але між ними не існує суттєвої різниці. Насправді, є причина віддати перевагу do. while як if. else побудувати.

Проблема побудови if. else полягає в тому, що він не змушує вас поміщати крапку з комою. Як в цьому коді:

Хоча ми залишили крапку з комою (помилково), код буде розширюватися до

і буде автоматично компілюватиметься (хоча деякі компілятори можуть видати попередження для недосяжного коду). Але оператор printf ніколи не буде виконано.

do. while конструкції немає такої проблеми, так як єдиний дійсний токен після while (0) є крапкою з комою.

Хоча очікується, що компілятори оптимізують дію do while (false); петлі, є інше рішення, яке не вимагало б цієї конструкції. Рішення полягає в використанні оператора коми:

або ще більш екзотично:

Хоча це буде добре працювати з окремими інструкціями, воно не буде працювати з випадками, коли змінні створюються і використовуються як частина #define.

З цим можна було б використовувати конструкцію do / while.

Препроцесорну бібліотека Jens Gustedt (так, той факт, що така річ існує, теж підірвала мій розум!) Покращує конструкцію if (1) else невеликим, але значним способом, визначаючи наступне:

Обгрунтуванням для цього є те, що, на відміну від do while (0). break і continue продовжують працювати всередині цього блоку, але ((void) 0) створює синтаксичну помилку, якщо крапка з комою опущена після макро-виклик, який в іншому випадку пропустить наступний блок. (Насправді тут немає проблеми з «базікання», так як else прив'язується до найближчого if. Який є таким в макросі.)

Якщо вас цікавлять ті речі, які можна зробити більш-менш безпечно з препроцесором C, ознайомтеся з цією бібліотекою.

Деякі з вас показали макроси з локальними змінними, але ніхто не згадував, що ви не можете просто використовувати будь-яке ім'я в макросі! Він вкусить користувача в один прекрасний день! Навіщо? Оскільки вхідні аргументи підставляються в ваш шаблон макросу. І в ваших прикладах макросів ви використовуєте, ймовірно, найбільш часто використовується змінне ім'я i.

Наприклад, коли наступний макрос

використовується в наступній функції

макрос не використовуватиме передбачувану змінну i, оголошену на початку some_func, але локальну змінну, оголошену в циклі do. while.

Таким чином, ніколи не використовуйте імена загальних змінних в макросі!

будуть переведені на

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

Краще, ніж do <> while (0) і if (1) <> else. можна просто використовувати (<>).

І цей синтаксис сумісний з повертаються значеннями (do <> while (0) is not), як в:

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