2016-02-14 1 views
8

В некотором коде C99 мне нужно проверить, не является ли переменная i в интервале [0, max], где max, как известно, является положительным. Проблема в том, что тип переменной разрешен как подписанный, так и неподписанный (путем изменения typedef). Как лучше проверить, что переменная находится в интервале?Проверьте, находится ли переменная неизвестной подписи в интервале

Прямое подход будет:

bool is_in_interval(my_type i, my_type max) { 
    assert(max > 0); 
    return (i >= 0 && i <= max); 
} 

Это будет работать хорошо, когда у нас есть typedef int my_type;. Но когда my_type является неподписанным (то есть, typedef unsigned int my_type;), i >= 0 всегда истинно, и компиляторы (по праву) предупреждают об этом, чего я хочу избежать. (Я не хочу отключать это предупреждение, так как это полезно, когда такие сравнения действительно непреднамеренны, и просто игнорирование предупреждений компилятора - это не очень хорошая идея.)

Моя нынешняя идея состоит в том, чтобы сделать i неподписанным типом и просто проверить верхнюю границу:

bool is_in_interval(my_type i, my_type max) { 
    assert(max > 0); 
    return ((size_t) i <= (size_t) max); 
} 

Если подписанный тип имеет комплемента представление в две, любое отрицательное значение должно быть больше, чем когда-то max гипсе без знака версии, не так ли? Если это так, это должно сработать. Однако я не уверен, что это целесообразный подход. Можно ли, например, с уверенностью предположить, что подписанные типы используют два дополнения на всех платформах? Есть ли лучший способ сделать это?

+0

Это не сработает, если ваше подписанное тестовое значение, интерпретируемое как 'unsigned', может быть больше (без знака)' MAX/2'. Так ли это? (Возможно, я имею в виду * меньше *.) – usr2564301

+2

Я уверен, что гарантировано, что отрицательное значение, переданное совместимому неподписанному типу, будет больше, чем любое положительное значение для исходного типа. Однако, если вы это сделаете, вы будете застонать о «странном коде». – sh1

+0

@Jongware Извините, я не совсем понимаю, что вы имеете в виду. 'MAX' не является пределом типа (например,' INT_MAX' или 'UINT_MAX'). Использование заглавных букв вводило в заблуждение (теперь это изменилось). –

ответ

7

Проверить против 0 и max. Однако избегайте прямого теста >= 0.

Кастинг - проблема, поскольку она может сузить значение в определенной реализации определенным образом.

bool is_in_interval(my_type i, my_type max) { 
    // first, take care of the easy case 
    if (i > max) return false; 
    if (i > 0) return true; 
    return i == 0; 
} 

Я обдумывать это еще немного слишком.

+0

Спасибо! Не могли бы вы подробно остановиться на «Кастинг - проблема, поскольку она может сузить значение в определенной реализации определенным образом»? Не совсем понимаю, что вы имеете в виду. –

+1

@Fredrik Savje Кастинг 'long long' (может быть, тип' my_type') для 'unsigned' хорошо определен, но, вероятно, теряет некоторый диапазон и' ((size_t) i <= (size_t) max) 'тогда бессмысленно поскольку приведение может изменить значение 'max', но не' i'. – chux

+0

Конечно, я ошибочно думал, что целочисленный тип не может быть больше, чем 'size_t'. Спасибо за разъяснения! –

2

Может быть, это еще один простой подход:

bool is_in_interval(my_type i, my_type max) { 
    assert(max > 0); 
    if ((my_type)-1 > 0) 
     return (i <= max); // my_type is unsigned 
    else 
     return (0 <= i && i <= max); // my_type is signed 
} 
+0

Не будет 'if ((my_type) -1> 0)' создать предупреждение, поскольку оно всегда верно или false. Очень похоже, что OP всегда «истина» и компиляторы будут (правильно) предупреждать »? – chux

+0

Ну, clang дает мне предупреждение, но он находится на 'return (0 <= i && i <= max); // my_type подписан ', говоря, что сравнение 0 <= unsigned expression всегда истинно'. –

3

Я думаю, что это должно закрыть компилятор до:

bool is_in_interval(my_type i, my_type max) { 
    my_type zero = 0; 
    assert(max > 0); 
    return (zero <= i && i <= max); 
} 
+0

Он закроет некоторые компиляторы, но не все. Компиляторы отличаются тем, что тестирование против объявленной переменной 'const' вызовет предупреждения типа «condition always true» или «condition always false». – Peter

+0

@Peter Я вижу, спасибо!Тогда он стал менее привлекательным. –

+0

Я мог бы предложить 'volatile' вместо' const', но может быть лучше найти встроенную аннотацию конкретного компилятора, чтобы отключить предупреждение. – sh1

2

Если ваш компилятор поддерживает это, я считаю, что чистое решение будет держать код как есть, но использовать #pragma директиву локально отключить ложное предупреждение. Например, для GCC 4.6.4+, следующий будет do the trick:

bool is_in_interval(my_type i, my_type max) { 
    assert(max > 0); 
#pragma GCC diagnostic push 
#pragma GCC diagnostic ignored "-Wtype-limits" 
    /* i >= 0 could warn if my_type is unsigned */ 
    return (i >= 0 && i <= max); 
#pragma GCC diagnostic pop 
} 

По-видимому, точно такой же #pragma с should work for Clang too, так как он имитирует синтаксис GCC в. Другие компиляторы могут иметь аналогичный синтаксис (например, #pragma warning(disable: ...) для Visual C/C++), и должно быть возможно использовать директивы препроцессора для выбора подходящего #pragma s для каждого компилятора.

Основной недостаток этого метода является эстетичным — он поместил код с уродливым #pragma s. Тем не менее, я считаю, что предпочтительнее преднамеренно запутывать (и, возможно, де-оптимизировать) ваш код, чтобы избежать предупреждения о компиляторе. По крайней мере, с #pragma s, это ясно для следующего программиста, который читает код, почему он написан так, как он есть.


Чтобы сделать это чище и более портативными, вы можете использовать C99 _Pragma() operator для определения макросов, которые отключают и повторно включить предупреждения, например, как это:

#if __GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ >= 40604 
# define NO_TYPE_LIMIT_WARNINGS \ 
     _Pragma("GCC diagnostic push") \ 
     _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") 
# define RESTORE_WARNINGS \ 
     _Pragma("GCC diagnostic pop") 
/* Add checks for other compilers here! */ 
#else 
# define NO_TYPE_LIMIT_WARNINGS /* nothing */ 
# define RESTORE_WARNINGS /* nothing */ 
#endif 

и использовать их как это:

bool is_in_interval(my_type i, my_type max) { 
    assert(max > 0); 
    NO_TYPE_LIMIT_WARNINGS; /* i >= 0 could warn if my_type is unsigned */ 
    return (i >= 0 && i <= max); 
    RESTORE_WARNINGS; 
} 

В качестве альтернативы, если вы хотите, чтобы поддержать предварительные C99 компиляторы, вы можете создать пару файлов заголовков (без любого включают охрану !) назвали что-то вроде warnings_off.h и warnings_restore.h, которые содержат соответствующий #pragma для каждого компилятора, а затем скопируйте код, для которого вы хотите отключить предупреждения, для:

#include "warnings_off.h" 
/* ...code that emits spurious warnings here... */ 
#include "warnings_restore.h" 
+0

Мне нравится эта идея, но вижу большую нижнюю сторону переносимости: есть много компиляторов (и программистов), не связанных с gcc C, включая, возможно, один из OP, который должен был бы переписать код '' ifdef''. – chux

+0

Спасибо! В некотором смысле это, вероятно, «правильный» способ сделать это. Все решения, представленные до сих пор, - это в основном способы обмануть компилятор, чтобы он не предупреждал о чем-то, о чем ему следует предупреждать, - очевидным решением было бы просто временно отключить предупреждение. Проблема здесь в том, что я пишу небольшую библиотеку C, поэтому я не могу полагаться на конкретный компилятор. –

2

Простым решением является проведение двухстороннего теста.

Единственный атрибут, действительно потерянный, - это скромная эффективность/производительность. Никаких других проблем.

Несмотря на то, что ОП, безусловно, считал это, любое решение следует сопоставить с этим как ссылку.

bool is_in_interval2(my_type i, my_type min, my_type max) { 
    return i >= min && i <= max; 
} 

// Could make this a macro or inline 
bool is_in_interval(my_type i, my_type max) { 
    return is_in_interval2(i, 0, max); 
} 
Смежные вопросы