Является `предупреждение C4127` (условное выражение является постоянным) когда-либо полезным?

Отвечая на this, я предложил использовать do {...} while(0) для многострочных макросов.

В MSVC я обнаружил, что этот код подбрасывает:

warning C4127: conditional expression is constant

Чтобы сделать код без предупреждения, мне нужно выбрать одну из этих уродливых альтернатив:

Вариант 1

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4127)
#endif
code_using_macro_that_generates_C4217;
#ifdef _MSC_VER
#pragma warning(pop)
#endif

Вариант 2
Определите мои макросы как:

#define MULTI_LINE_MACRO do { ... } while(0,0)

или

#define MULTI_LINE_MACRO do { ... } while((void)0,0)

Также называется "сова" некоторыми программистами, поскольку (0,0) выглядит как сова.

Вариант 3
Определите новый макрос WHILE_0, который не генерирует предупреждение и использует его вместо while(0)

Проблема
Я считаю, что все альтернативы более или менее ужасны. Почему MSVC генерирует это предупреждение для кажущегося правильного кода и мотивирует меня добавить некоторое уродство в мой код, чтобы сохранить код предупреждения бесплатно?

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

Кроме того, я не получаю warning C4127 для кода, подобного этому:

void foo(unsigned bar)
{
    while (bar >= 0)
        ;
} 

Мой вопрос: не является ли warning C4127: conditional expression is constant совершенно бесполезным и не мотивирует ли он уродливый код? Предупреждает ли это предупреждение о написании лучшего кода?

Ответы

Ответ 1

Я не думаю, что это когда-нибудь полезно. Напротив, есть более ложные срабатывания, чем только идиома do .. while(0). Подумайте о конструкциях типа

if(sizeof(long) == 8) { /* ... */ }
if(SOME_CONSTANT_MACRO) { /* ... */ }

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

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

Из приведенных до сих пор примеров (у меня нет MSVC для тестирования самостоятельно), похоже, что предупреждение предназначено для постоянных выражений в смысле языка C (то есть не то, что может быть постоянным) сложенное, но синтаксически не является постоянным выражением), поэтому он не выделяется для if(array) или if(function) (что, например, предупреждает gcc -Wall, потому что он, вероятно, предназначен для вызова функции).

while(0,0) хуже, на мой взгляд, он вызывает предупреждение с gcc -Wall для левой части оператора запятой без побочных эффектов, предупреждение, которое я могу себе представить, иногда полезно (и обычно это легко избежать). Это предупреждение исчезает с помощью while((void)0,0).

Я предлагаю отключить предупреждение.

Ответ 2

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

Например, если вы пишете что-то вроде:

if (x != NULL) { /* ... */ }

где x - объект массива, выражение справедливо, но выражение x распадается на указатель на начальный элемент массива, который не может быть нулевым указателем. Я не знаю, вызывает ли та же ошибка, но это пример того же типа.

Конечно, это не всегда полезно. В вашем случае

do { /* ... */ } while (0)

Идиома - лучший способ написать определение макроса, которое предназначено для использования в контексте, требующем утверждения. Использование while (0) в другом контексте скорее всего будет логической ошибкой [*].

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

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

Очевидно, что запись while (0,0), а не while (0) позволяет избежать предупреждения. Если вы это сделаете, вы должны добавить комментарий, четко указывающий, что это обходной путь для вашего конкретного компилятора. Нет особых причин, по которым компилятор не должен предупреждать о while (0,0) или любом другом эквивалентном коде.

[*] Имеет смысл написать оператор do { /* ... */ } while (0), если вы хотите использовать break для выпрыгивания из него.