Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

Зачем нужна конструкция do { … } while(0)?


В библиотеке freeglut в файле freeglut_callbacks.c нашел такой код:

/*
 * All of the callbacks setting methods can be generalized to this:
 */
#define SET_CALLBACK(a)                                         \
do                                                              \
{                                                               \
    if( fgStructure.CurrentWindow == NULL )                     \
        return;                                                 \
    SET_WCB( ( *( fgStructure.CurrentWindow ) ), a, callback ); \
} while( 0 )


Не понимаю, зачем здесь нужен цикл, ведь он в любом случае выполниться всего один раз, разве нет?
    


Ответы

Ответ 1



Конструкция широко используется в "функциональных" макросах и предназначена для объединени нескольких statement и объявлений внутри макроса в один составной statement. Такое объединени нужно для того, чтобы макрос можно было использовать как обычный вызов функции в ветках условного оператора, циклах и т.п. без необходимости постоянно заключать этот вызов во внешнюю пару { ... } На первый взгляд того же самого можно было бы достичь просто путем заключения самог определения макроса в пару { ... } вместо do { ... } while (0). Однако вариант с { ... } будет обладать одним неприятным недостатком - надо будет постоянно помнить, что его тело представляет из себя { ... }, а после { ... } в С не всегда можно ставить ;. Например, если бы тело SET_CALLBACK было заключено просто в { ... }, то вот тако естественно выглядящий код не скомпилировался бы по причине того, что из-за поставленной после SET_CALLBACK(foo) точки с запятой ветка else оказалось оторванной от своего if if (true) SET_CALLBACK(foo); // <- "Лишняя" `;` else // <- Ошибка: оторванный `else` /* что-то еще */; Вот тут-то на сцену и выходит do { ... } while (0). Цикл do/while уникален тем, чт является единственной (почти) в языке С грамматической конструкцией, которая формируе блок, и при этом всегда безусловно заканчивается на ;. Благодаря этой замыкающей ; м можем более естественным образом использовать do { ... } while (0) внутри составных макросов для объединения их в единый statement. В вышеприведенном примере с if ;, указанная после SET_CALLBACK(foo), будет "поглощена" конструкцией do { ... } while (0) и не приведет в "разрушению" целостности if. Вот именно ради того, чтобы иметь возможность "вызывать" функциональные макросы чере тот же естественный синтаксис, через который мы вызываем обычные функции, тело составного макроса и заключают не в { ... }, а именно в do { ... } while (0) P.S. Почему авторы языка в свое время решили настоять на включении [теоретическ совсем не нужной] замыкающей ; в синтаксис do/while - не совсем ясно, но это было сделано.

Ответ 2



Это очень полезная конструкция. Так как макросы подставляются "как есть", то могут происходить различные чудные вещи. Допустим у нас есть такой макрос #define foo(x) int x;\ x=1;\ printf("%d\n",x); (пример надуманный) И если теперь вызвать вот так if (...) foo(a); то это развернется в такое if (...) int a; a=1; printf("%d\n",a); и естественно, будет работать совсем не так, как того ожидает применяющий - объявление переменной явно не там и скобок не хватает. В принципе можно было бы обойтись просто фигурными скобками, но конструкция с do-while(0 дает один существенный плюс - возможность использовать break для выхода с макроса, что ещё больше приближает макрос к обычной функции.

Комментариев нет:

Отправить комментарий