C / C ++: как использовать do-while (0); построить без предупреждений компилятора, например C4127?

Я часто использую конструкцию do-while (0) в моих #defines по причинам, описанным в этом ответе. Также я пытаюсь использовать как можно более высокий уровень предупреждений от компилятора, чтобы выявить больше потенциальных проблем и сделать мой код более надежным и кроссплатформенным. Поэтому я обычно использую -Wall с gcc и /Wall с MSVC.

К сожалению, MSVC жалуется на конструкцию do-while (0):

foo.c(36) : warning C4127: conditional expression is constant

Что мне делать с этим предупреждением?

Просто отключить глобально для всех файлов? Мне это не кажется хорошей идеей.


person bialix    schedule 22.12.2009    source источник
comment
Поскольку вы добавили тег C ++, возможно ли преобразовать макрос #define во встроенные функции? Так было бы безопаснее.   -  person Thomas Matthews    schedule 22.12.2009
comment
Я также добавил тег C, поэтому я подумал, что спросил о C-совместимом решении. Должен ли я тогда удалить тег C ++?   -  person bialix    schedule 22.12.2009
comment
Вы пробовали такое условие, как sizeof (char)! = 1 ...?   -  person Tony Delroy    schedule 24.02.2011
comment
@bialix: Да, поскольку языки и решения сильно различаются. Удалите языковой тег, для которого вам не нужны ответы   -  person Mooing Duck    schedule 19.07.2016


Ответы (21)


Описание: Это предупреждение (C4127) в данном конкретном случае является тонкой ошибкой компилятора. Не стесняйтесь отключать его.

Глубоко:

Он был предназначен для обнаружения ситуаций, когда логическое выражение оценивается как константа в неочевидных ситуациях (например, if(a==a && a!=a), и каким-то образом это превратило while(true) и другие полезные конструкции в недопустимые.

Microsoft рекомендует использовать for(;;) для бесконечного цикла, если вы хотите, чтобы это предупреждение было включено, и для вашего случая нет решения. Это одно из очень немногих предупреждений четвертого уровня, которые моя компания разрешает отключать.

person Pavel Radzivilovsky    schedule 22.12.2009
comment
Я знаю о for (;;), но там это не применимо, как вы сказали. Я склонен следовать вашему совету, просто жду других предложений. - person bialix; 22.12.2009
comment
для меня, потому что (;;) просто выглядит некрасиво. - person Pavel Radzivilovsky; 23.12.2009
comment
Отключено и в моей компании (на самом деле, для каждого заголовка). Это сильно влияет на результат сборки при использовании ускорения. - person Alexandre C.; 27.01.2011
comment
@alexandre При использовании boost вы можете просто отключить его вокруг заголовков. #include warningsoff.h, затем warningson.h, чтобы вернуть его на желаемый уровень. - person Pavel Radzivilovsky; 28.01.2011
comment
Это то, что мы делаем, но мы вручную помещаем прагмы до и после заголовков повышения. - person Alexandre C.; 28.01.2011
comment
Я бы не назвал это предупреждение ошибкой компилятора. Он работает так, как задумано и задокументирован, и иногда обнаруживает настоящую ошибку. Я предпочитаю решение __pragma глобальному отключению предупреждения. - person Adrian McCarthy; 20.08.2012
comment
А как насчет if (1) {stuff;} else без точки с запятой в конце слова else? Или, может быть, do { stuff; if (always_true_condition) break; } while(1)? Конечно, do ... while(1) циклы с внутренними операторами break или return довольно распространены, как и if операторы с постоянными условиями. - person supercat; 16.09.2013
comment
В VS2015 его можно легко отключить в свойствах проекта / на странице свойств - ›Общие свойства -› C / C ++ - ›Дополнительно -› Отключить определенные предупреждения. Там вы можете добавить предупреждения, которые хотите отключить, через точку с запятой. - person mtb; 18.05.2016


Как отметил Майкл Берр в Carl Smotricz 'answer, для Visual Studio 2008+ можно использовать __ pragma:

#define MYMACRO(f,g)              \
  __pragma(warning(push))         \
  __pragma(warning(disable:4127)) \
  do { f; g; } while (0)          \
  __pragma(warning(pop))

Вы можете поместить его в одну строку (без \s), если хотите, чтобы макросы были нечитаемыми.

person Michel de Ruiter    schedule 27.01.2011
comment
Отправка выполняется слишком рано - вам нужно выполнить отправку предупреждения непосредственно перед while (0), если вы все еще хотите отловить проблемы в операторах f и g. См. Другой ответ MULTI_LINE_MACRO для чего-то неопределенно многоразового. - person Tom Whittock; 11.01.2015

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

#ifdef WIN32
#  define ONCE __pragma( warning(push) ) \
               __pragma( warning(disable:4127) ) \
               while( 0 ) \
               __pragma( warning(pop) )
#else
#  define ONCE while( 0 )
#endif

А я использую это так:

do {
   // Some stuff
} ONCE;

Вы также можете использовать это в макросах:

void SomeLogImpl( const char* filename, int line, ... );    

#ifdef NDEBUG
#  define LOG( ... )
#else
#  define LOG( ... ) do { \
      SomeLogImpl( __FILE__, __LINE__, __VA_ARGS__ ); \
   } ONCE
#endif

Это также работает для случая, указанного выше, если F использует ONCE в функции:

#define F( x ) do { f(x); } ONCE
...
if (a==b) F(bar); else someFunc();

Изменить: Спустя годы я понимаю, что забыл добавить шаблон, для которого на самом деле написал этот макрос - шаблон "switch-like-a-goto":

do {
    begin_some_operation();

    if( something_is_wrong ) {
        break;
    }

    continue_big_operation();

    if( another_failure_cond ) {
        break;
    }

    finish_big_operation();
    return SUCCESS;
} ONCE;

cleanup_the_mess();
return FAILURE;

Это дает вам конструкцию try / finally-ish, которая более структурирована, чем грубый переход к вашему коду очистки и возврата. Использование этого макроса ONCE вместо while (0) закрывает VS.

person nevelis    schedule 04.05.2013
comment
Это хороший способ выразить это. - person Joel Falcou; 18.08.2013

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

#define MY_MACRO(stuff) \
    do { \
        stuff \
    __pragma(warning(suppress:4127)) \
    } while(0)

Вы также можете нажать / отключить / нажать, но подавление - гораздо более удобный механизм.

person Seth Moore    schedule 11.09.2014
comment
Более удобный механизм обхода ошибки, нежели исправление ошибки. Хорошая работа, MS! - person Slava; 19.07.2016

Эта ошибка компилятора была исправлена ​​в Visual Studio 2015 с обновлением 1, даже если примечания к выпуску не упоминают об этом.

Однако ошибка была объяснена в одном из предыдущих ответов:

Описание: Это предупреждение (C4127) в данном конкретном случае является тонкой ошибкой компилятора. Не стесняйтесь отключать его.

Он был предназначен для обнаружения ситуаций, когда логическое выражение оценивается как константа в неочевидных ситуациях (например, if (a == a && a! = A), и каким-то образом оно превратило while (true) и другие полезные конструкции в недопустимые .

person chris.briend    schedule 16.04.2016

Вот еще один возможный подход, который позволяет избежать C4127, C4548 и C6319 (предупреждение анализа кода VS2013) и не требует макросов или прагм:

static const struct {
    inline operator bool() const { return false; }
} false_value;

do {
    // ...
} while (false_value);

Это оптимизирует и компилируется без предупреждений в GCC 4.9.2 и VS2013. На практике это может быть пространство имен.

person james    schedule 19.07.2016

Предупреждение связано с while(false). На этом сайте приведен пример решения этой проблемы. проблема. Пример с сайта (вам придется переделать его для своего кода):

#define MULTI_LINE_MACRO_BEGIN do {  
#define MULTI_LINE_MACRO_END \  
    __pragma(warning(push)) \  
    __pragma(warning(disable:4127)) \  
    } while(0) \  
    __pragma(warning(pop))

#define MULTI_LINE_MACRO \  
        MULTI_LINE_MACRO_BEGIN \  
            std::printf("Hello "); \  
            std::printf("world!\n"); \  
        MULTI_LINE_MACRO_END  

Просто вставьте свой код между BEGIN и END.

person Community    schedule 20.08.2012

Ты можешь использовать

do {
    // Anything you like
} WHILE_FALSE;

А ранее определите макрос WHILE_FALSE следующим образом:

#define WHILE_FALSE \
    __pragma(warning(push))         \
    __pragma(warning(disable:4127)) \
    while(false)                    \
  __pragma(warning(pop))

Проверено на MSVC ++ 2013.

person Serge Rogatch    schedule 24.08.2016

Эта штука «while (0)» - хакерская штука, которая только что повернулась, чтобы вас укусить.

Ваш компилятор предлагает #pragmas для выборочного и локального отключения определенных сообщений об ошибках? Если так, это может быть разумной альтернативой.

person Carl Smotricz    schedule 22.12.2009
comment
Да, MSVC поддерживает прагмы, но как я могу обернуть макросы в эту прагму? - person bialix; 22.12.2009
comment
Простите, не знаю. Это была идея, подсказка от меня, и я надеялся, что она вдохновит на решение. Я не помню, чтобы раньше сталкивался с подобными проблемами. - person Carl Smotricz; 22.12.2009
comment
C99 описывает _Pragma () как альтернативу форме препроцессора #pragma именно так, чтобы ее можно было сгенерировать как часть макроса. Microsoft заявила, что они не заинтересованы во внедрении C99, но я не знаю, есть ли эта функция уже (учитывая их поставщика внешнего интерфейса). - person Phil Miller; 22.12.2009
comment
В MSVC есть оператор препроцессора __pragma(), который, к сожалению, немного отличается от оператора _Pragma() в C99 (C99 принимает строковый литерал, MSVC принимает токены, которых нет в строке): msdn.microsoft.com/en-us/library/d9x1s805.aspx - person Michael Burr; 13.06.2010

Вы можете использовать оператор запятой вместо конструкции do-while (0) для макроса с несколькими операторами, который будет использоваться в выражениях. Так что вместо:

#define FOO(...)    do { Statement1; Statement2; Statement3; } while(0)

Использовать:

#define FOO(...)    (Statement1, Statement2, Statement3)

Это работает независимо от платформы и позволяет избежать предупреждения компилятора (даже если выбран самый высокий уровень предупреждения). Обратите внимание, что в запятой, содержащей макрос (второй FOO), результат последнего оператора (Statement3) будет результатом всего макроса.

person Armen Anoyan    schedule 07.10.2013

Должен сказать, я никогда не беспокоился о конструкции do.. while в макросах. Весь код в моих макросах заключен в фигурные скобки, но без do - .. while. Например:

#define F(x) \
    {           \
        x++;    \
    }           \

int main() {
    int a = 1;
    F(a);
    printf( "%d\n", a );
}

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

person Community    schedule 22.12.2009
comment
do-while (0) - широко используемая конструкция AFAIK. - person bialix; 22.12.2009
comment
Я знаю. Я предполагаю, что в этом нет необходимости. - person ; 22.12.2009
comment
+1 Возможно, он широко используется (во всяком случае, я никогда не видел его в производственном коде), но я не думаю, что это необходимо. В конце концов, следует избегать макросов, поэтому, если вы добавляете их, делайте их простыми. Не вводите циклы в мой код. - person Daniel Daranas; 22.12.2009
comment
Да, он широко используется, но он действительно необходим только в нескольких пограничных случаях, которых я все равно стараюсь избегать. В нормальном коде обычно достаточно обычных фигурных скобок, как показано здесь. - person jalf; 22.12.2009
comment
Иногда я забываю точки с запятой, и это делает код ужасным для чтения: / - person Johannes Schaub - litb; 22.12.2009
comment
@litb С кем ты говоришь? Если я, вы можете использовать или опустить точку с запятой. И какая часть кода выглядит ужасно? - person ; 22.12.2009
comment
@Tomas Смотрите мой второй абзац. - person ; 22.12.2009
comment
@Neil, твоя привычка кодировать не может быть применима ко всем. поэтому я думаю, что ваш ответ не является хорошим руководством. и некоторые строки с завершающей точкой с запятой, а некоторые строки без нее - это ужасно. - person CodingLab; 22.12.2009

Решение есть, но оно добавит больше циклов в ваш код. Не используйте явное значение в условии while.

Сделать это можно так:

file1.h

extern const int I_am_a_zero;
#define MY_MACRO(foo,bar) \
do \
{ \
} \
while(I_am_a_zero);

переменная I_am_a_zero должна быть определена в каком-нибудь .c файле.

В любом случае это предупреждение не отображается в GCC :)

См. Этот связанный с этим вопрос.

person Yousf    schedule 22.12.2009
comment
Разве эта константная переменная не помешает оптимизации цикла и не внесет ненужную проверку на ноль? - person bialix; 22.12.2009
comment
Да, именно поэтому Юсф сказал, что добавит больше циклов в ваш код. - person jamesdlin; 22.12.2009
comment
нет, компилятор не будет знать, что переменная I_am_a_zero на самом деле равна нулю при компиляции; Потому что это внешний символ, который будет выполнен на этапе связывания. - person Yousf; 23.12.2009

Вы можете использовать #pragma warning, чтобы:

  1. спасти государство
  2. отключить предупреждение
  3. напишите оскорбительный код
  4. вернуть предупреждение в их предыдущее состояние

(вам нужен # перед прагмами, но SO трудно справиться с ними и форматировать одновременно)

#pragma warning( push )
#pragma warning( disable: 4127 )
// Your code
#pragma warning( pop ) 

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

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

person TofuBeer    schedule 22.12.2009
comment
Я не уверен, как я могу сделать это частью макроса. Если я поставлю прагмы сразу после #define foo(x) \ , у меня будет ошибка: ошибка C2162: ожидаемый формальный параметр макроса - person bialix; 22.12.2009
comment
У меня нет под рукой компилятора MS ... так что не могу это проверить. Я думаю, оберните использование макроса в этом коде, если вы не можете поместить его в макрос. Вы уверены, что сделали это как #pragma? (просто проверка :-) - person TofuBeer; 22.12.2009
comment
Вы не можете помещать прагмы в макрос, поэтому вам придется отключить предупреждение везде, где вы используете макрос. Противный. - person Graeme Perrow; 22.12.2009
comment
Bialix: расширьте макрос до вызова встроенной функции. Поместите do-while в эту функцию и оберните ее прагмой. - person Ashwin Nanjappa; 24.02.2011

Что ж, для меня без предупреждения C4127 работает следующее:

#define ALWAYS_TRUE(zzsome) ((##zzsome)==(##zzsome))

void foo()
{
    int a = 0;
    while( ALWAYS_TRUE(a) )
    {
    }
}

Конечно, компиляторы умны и zzsome не должно быть постоянным

person Deyan Ivanov    schedule 13.08.2015

Это отключит предупреждение, и компилятор по-прежнему сможет оптимизировать код:

static inline bool to_bool(const bool v) { return v; }

if (to_bool(0)) { // no warning here
    dead_code(); // will be compiled out (by most compilers)
}

do { something(); } while(to_bool(0)); // no extra code generated
person wonder.mice    schedule 19.12.2015

Я нашел, что это самая короткая версия

do {
  // ... 
}
while (([]() { return 0; })())  /* workaround for MSVC warning C4172 : conditional expression is constant */

Не проверял, оптимизирован ли он компилятором, но я предполагаю, что это так.

person Niklas R    schedule 09.02.2016
comment
он оптимизирован прочь. - person bolov; 09.02.2016
comment
и вы можете избавиться от одной пары скобок: } while ([]() { return 0; }()); - person bolov; 09.02.2016

Вы можете использовать цикл for как:

for (;;) {
  // code
  break;
}

Макро:

#define BEGIN \
  for (;;) {

#define END \
  break; }
person wolfram77    schedule 04.10.2016

Я бы использовал

for(int i = 0; i < 1; ++i) //do once
{

}

Это эквивалентно

do
{
}while(0);

и не дает никаких предупреждений.

person Armen Tsirunyan    schedule 19.10.2012
comment
Вы упустили суть, do { } while(0) обратите внимание, что отсутствие точки с запятой, используется в макросах по какой-то причине - person Motti; 09.09.2013

Используйте переключатель компилятора / wd "4127", чтобы отключить это предупреждение в своем проекте.

person Naveed Rasheed    schedule 03.02.2020
comment
OP специально включил все предупреждения, так зачем предлагать отключать? Это, в первую очередь, противоречило бы цели включения предупреждения. - person ChrisMM; 03.02.2020
comment
Это опять же, в первую очередь, противоречит цели его включения. Может быть много других мест, где OP должна быть включена проверка в MSVC. - person ChrisMM; 04.02.2020
comment
Я предложил отключить это предупреждение только для msvc. Он по-прежнему может получать все предупреждения с помощью gcc. Каждый компилятор имеет разные политики для создания отчетов о предупреждениях. msvc строг для определенных типов предупреждений, тогда как gcc. Вы можете отключить это предупреждение, зная, что предложение msvc не требуется. Для меня более выгодно включить все предупреждения во всех компиляторах, а затем отключить некоторые из них для этого конкретного компилятора, которые являются слишком строгими или ложными. - person Naveed Rasheed; 04.02.2020
comment
@ChrisMM, что не понимают, что не существует такой вещи, как панацея. Каждое решение служит в каком-то конкретном случае. Вы должны увидеть принятый ответ, где также рекомендуется отключить это предупреждение. - person Naveed Rasheed; 04.02.2020
comment
Отключение предупреждения после включения всех предупреждений приводит к поражению цели включения всех предупреждений. Я не уверен, что в этом сложного. Я понимаю, что вы говорите только о MSVC, но что, если это основная среда разработки? Может быть, OP отходил от gcc? OP конкретно указал Просто отключить его глобально для всех файлов? Мне кажется, это не очень хорошая идея, где четко указано, что предложенное вами решение не им подходит. И, как вы упомянули, в других ответах уже предлагается отключить, так что на этом этапе что добавляет ваш ответ? - person ChrisMM; 04.02.2020
comment
Если компилятор может сообщать о 100 категориях предупреждений, а вы просто хотите отключить одну из них и включить другие 99 категорий, как бы вы это сделали? Чтобы ответить на ваш вопрос, мой ответ добавляет, как вы можете отключить это предупреждение с помощью переключателя компилятора на уровне проекта в msvc. - person Naveed Rasheed; 04.02.2020

person    schedule
comment
Я вряд ли ожидаю, что кто-то, использующий MSVC, будет компилировать простой C. - person ephemient; 22.12.2009
comment
Ладно, тогда я тот сумасшедший. Кстати, расширения C для Python обычно компилируются с помощью MSVC в Windows. - person bialix; 22.12.2009
comment
+1 Если вы компилируете C ++, этот первый макрос хорош тем, что он переносится и не требует глобального изменения среды сборки. Второй страдает от проблемы с зависанием else, для решения которой предназначено решение do {} while (false). - person Adrian McCarthy; 20.08.2012