Do {...} while (0) - для чего это нужно?

Possible Duplicate:
Why are there sometimes meaningless do/while and if/else statements in C/C++ macros?

Я наблюдаю это выражение уже более 10 лет. Я долго думал, для чего это нужно. Поскольку я вижу это в основном в #defines, я полагаю, что это хорошо для объявления переменных внутренней области видимости и для использования разрывов (вместо gotos).

Это хорошо для чего-нибудь еще? Вы им пользуетесь?

Взгляните на этот вопрос.

Federico A. Ramponi 03.11.2008 00:38

На самом деле, это не дубликат, поскольку связанный q / a не является специфическим для определения. Легко сравнить оба ответа, чтобы сказать, что это не дубликат.

Doomsday 18.05.2012 19:44

См. «Decment_used_memory» в строке 53 Redis [ссылка] github.com/antirez/redis-tools/blob/master/zmalloc.c

yet 10.12.2012 01:57

Дубликат с вопросом, помеченным как возможный дубликат (первая строка сообщения), а не с вопросом, заданным Федерико А. Рампони.

Étienne 02.05.2013 12:52

Предлагаемый дубликат предназначен только для использования в макросах. Этот вопрос касается общего использования.

dcow 18.11.2015 07:41

другие варианты этого do { ... } while ((void)0, 0) используются для отключения предупреждений компилятора о "постоянном состоянии".

Amro 01.01.2016 03:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
369
6
158 908
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Это способ упростить проверку ошибок и избежать глубоких вложений if. Например:

do {
  // do something
  if (error) {
    break;
  }
  // do something else
  if (error) {
    break;
  }
  // etc..
} while (0);

э-э, разложите его на метод и используйте ранний возврат.

Dustin Getz 03.11.2008 02:35

или ... просто используйте do {} while (0).

nickf 03.11.2008 02:48

Или используйте goto. Нет, серьезно, if (error) goto error; и error: ... ближе к концу кажутся мне более чистой версией, которая выполняет то же самое.

FireFly 23.04.2014 21:46

@FireFly О, как goto fail? (конечно, goto не был ошибкой, что на самом деле делает ее еще смешнее)

wchargin 13.06.2014 09:11

@WChargin: код «goto fail» в статье, на которую вы ссылаетесь, тоже не прошел бы с «перерывом». Кто-то просто продублировал там строчку. Гото не виноват.

Niccolo M. 15.06.2014 15:15

@NiccoloM. "конечно, goto не было ошибкой, что на самом деле делает его еще смешнее"

wchargin 15.06.2014 21:36

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

Gastón Saillén 07.08.2019 18:19

не делает его "смешнее", просто делает его совершенно бессмысленным комментарием

Lightness Races in Orbit 11.12.2019 17:47

Как правило, do / while подходит для любого вида конструкции цикла, где нужно выполнить цикл по меньшей мере один раз. Можно имитировать такой вид цикла либо через прямой цикл while, либо даже через цикл for, но часто результат оказывается немного менее элегантным. Я признаю, что конкретные применения этого паттерна довольно редки, но они существуют. На ум приходит консольное приложение на основе меню:

do {
    char c = read_input();

    process_input(c);
} while (c != 'Q');

Он также доступен на C#, в котором нет макросов. Я не уверен, почему кто-то проголосовал против этого ответа, но я считаю его почти правильным, за исключением того, что он упустил из виду явный «0» в условии while. Пожалуйста, народ, если вы проголосовали против чей-то ответ, прокомментируйте.

Jon Davis 03.11.2008 01:36

Я не был тем, кто проголосовал против; однако вопрос очень конкретный, и в целом ответ верен, но вне контекста.

tzot 03.11.2008 02:04

Утверждение верно, но не в контексте.

Nadeem Khan 18.03.2019 13:08

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

#define FOO(n)   foo(n);bar(n)

а вы делаете:

void foobar(int n) {
  if (n)
     FOO(n);
}

затем это расширяется до:

void foobar(int n) {
  if (n)
     foo(n);bar(n);
}

Обратите внимание, что второй вызов bar(n) больше не является частью оператора if.

Оберните оба в do { } while(0), и вы также можете использовать макрос в операторе if.

Очень четкий ответ. +1

Anirudh Ramanathan 23.06.2015 21:57

Понятно. ;)

Whoami 06.07.2016 09:04
Ответ принят как подходящий

Это единственная конструкция в C, которую вы можете использовать для #define в многопользовательской операции, поставить точку с запятой после нее и по-прежнему использовать в операторе if. Пример может помочь:

#define FOO(x) foo(x); bar(x)

if (condition)
    FOO(x);
else // syntax error here
    ...;

Даже использование скобок не помогает:

#define FOO(x) { foo(x); bar(x); }

Использование этого в операторе if потребовало бы, чтобы вы опускали точку с запятой, что противоречит здравому смыслу:

if (condition)
    FOO(x)
else
    ...

Если вы определите FOO так:

#define FOO(x) do { foo(x); bar(x); } while (0)

то синтаксически верно следующее:

if (condition)
    FOO(x);
else
    ....

Разве #define FOO(x) if (true){ foo(x); bar(x); } else void(0) не будет работать, даже если он намного уродливее?

Adisak 08.08.2013 02:39

Но как насчет #define FOO(x) foo(x), bar(x), чтобы использовать запятую вместо точки с запятой. Разве это не сработало бы в другом случае?

user10607 03.11.2014 22:42

@ user10607: Да, это работает, если ваши расширения макросов представляют собой список выражения. Однако, если вы хотите включить if или while в расширение, этот трюк тоже не сработает.

Greg Hewgill 03.11.2014 22:45

@Adisak, я думаю, это сломает ваш пример: FOO(x) , bar();, потому что ,bar() будет поглощен void(0),bar(). т.е. оператор запятой будет применен не в том месте, что приведет к странному поведению. do { ... } while(0) очень хорошо зарекомендовал себя, делает то, что должен делать. Самое главное, когда опытные разработчики видят do { ... } while(0), написанный другими, они могут точно видеть, что происходит. Зачем использовать что-то еще, что могло бы беспокоить только опытных разработчиков, даже если это может быть правильным?

Aaron McDaid 25.06.2015 16:04

@AaronMcDaid Что ж, оператор запятой действительно лажает, как вы упомянули. Но это также плохо сказывается на деле. Это недопустимо для C++ 'do {} while (0), printf ("hello world \ n");' либо. Я указываю вам на этот тест: ideone.com/TdN4YG Итак, мой уродливый макрос работает так же хорошо, как и макрос do while.

Adisak 26.06.2015 20:49

Кроме того, я никогда не говорил, что мой уродливый макрос лучше, чем версия do-while. На самом деле, я предпочитаю делать время. Я просто опровергал утверждение в ответе, что do-while - ЕДИНСТВЕННЫЙ способ сделать это.

Adisak 26.06.2015 20:50

Ах! Согласен @Adisak. Это интересно. Я полагаю, что do {...} while(0) - это традиционный способ, и поэтому лучше быть последовательным и придерживаться его, если только альтернатива не делает то, чего не может do {...} while(0).

Aaron McDaid 27.06.2015 15:10

Я по-прежнему считаю, что это УЖЕСТВЕННОЕ и НЕКОТОРЫЕ кодирование, и его МОЖНО избежать, если оно написано правильно. это восходит к некоторым правилам C здравого смысла: ВСЕГДА использовать фигурные скобки в ваших операторах IF, даже если у вас есть только ОДНА операция после IF. Это должно быть стандартной практикой, ИМВХО ...

LarryF 16.07.2015 23:37

@LarryF: Проблема в том, что если вы пишете файл заголовка для использования другими, вы не можете выбирать, как другие люди будут использовать ваш макрос. Чтобы избежать неожиданных сюрпризов, нанесенных вашим пользователям, вы должны использовать метод, подобный описанному выше, чтобы заставить ваш макрос вести себя так, как любой другой оператор C.

Greg Hewgill 17.07.2015 02:39

Как замечательно, что этот ответ получил отрицательные голоса

Victor Wong 03.12.2015 12:17

Мне кажется, что использование или неиспользование точки с запятой не имеет значения?

Max 28.10.2016 06:33

@Max Вы используете свой макрос в операторе if без фигурных скобок, как в моем первом примере? В большинстве других ситуаций точка с запятой не имеет большого значения.

Greg Hewgill 28.10.2016 06:57

да. Я делаю, если (правда) {FOO (x); } и if (true) {FOO (x)}, точка с запятой и точка с запятой не имеют значения без do while (), но случай, когда используется do while (), не компилируется без точки с запятой

Max 28.10.2016 07:05

@Max Как и в моем примере, вам нужно, чтобы нет использовал фигурные скобки, и использовал предложение else.

Greg Hewgill 28.10.2016 08:14

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

Max 28.10.2016 08:21

@Max, хорошо, если у вас все еще есть вопросы о том, что с вами происходит, задайте новый вопрос вместо комментариев. Спасибо.

Greg Hewgill 28.10.2016 09:10

Любой, кто видит это сейчас, может найти это информативным: gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/… «Составной оператор, заключенный в круглые скобки, может отображаться как выражение в GNU C. Это позволяет вам использовать циклы, переключатели и локальные переменные в выражении». . Поэтому мы можем просто использовать #define FOO (x) ({foo (x); bar (x);})

abjoshi - Reinstate Monica 28.11.2018 11:01

@GregHewgill: извините, но я полностью согласен с LarryF: если вы настолько ленивы, что не ставите фигурные скобки, когда выражение только одно, ну, вы заслуживаете, что мой код не работает для вас. Не говоря уже о том, что не ставить фигурные скобки - это всегда - плохая идея, поскольку если вы добавите еще один оператор после if без фигурных скобок, возможно, вы подумаете, что он будет выполнен, только если вы введете if, но это не так.

Marco Sulla 01.11.2019 00:10

Я не понимаю, почему брекеты не работают.

Aaron Franke 05.12.2019 04:50

@AaronFranke, потому что {...}; фактически состоит из двух операторов: части {...} и еще одного пустого оператора после нее. делать {...} в то время как (0); это одно заявление, тем не менее. Ларри / Марко, когда пользователи пишут FOO (a, b, c), они ожидают (если явно не указано иное) результатом будет выражение, очень похожее на вызов функции, возвращающей void. Ваш подход на самом деле небрежный. Соглашение о стиле кода вашего пользователя не должно вас беспокоить.

yonil 26.06.2020 13:06

Интересно отметить следующую ситуацию, когда цикл do {} while (0) не будет работает за вас:

Если вам нужен макрос, похожий на функцию, который возвращает значение, вам понадобится выражение выражения: ({stmt; stmt;}) вместо do {} while (0):


#include <stdio.h>

#define log_to_string1(str, fmt, arg...) \
    do { \
        sprintf(str, "%s: " fmt, "myprog", ##arg); \
    } while (0)

#define log_to_string2(str, fmt, arg...) \
    ({ \
        sprintf(str, "%s: " fmt, "myprog", ##arg); \
    })

int main() {
        char buf[1000];
        int n = 0;

        log_to_string1(buf, "%s\n", "No assignment, OK");

        n += log_to_string1(buf + n, "%s\n", "NOT OK: gcc: error: expected expression before 'do'");

        n += log_to_string2(buf + n, "%s\n", "This fixes it");
        n += log_to_string2(buf + n, "%s\n", "Assignment worked!");
        printf("%s", buf);
        return 0;
}

Это расширение GCC. Однако в C++ 11 вы можете сделать то же самое с лямбда-выражением.

celticminstrel 08.07.2015 09:32

Выражения оператора TIL. Аккуратный. Как это выглядит с лямбдой? Мне любопытно. Лямбда возвращает функтор, по сути, вам все равно нужно вызвать его, а затем вернуть результат из макроса. и лямбда, и вызов должны формировать единый оператор. Как ты это делаешь? оператор ,?

yonil 26.06.2020 13:17

Другие вопросы по теме