在某些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 :(得分:8)
检查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;
}
我也会更多地思考这个问题。
答案 1 :(得分:3)
我认为这应该关闭编译器:
bool is_in_interval(my_type i, my_type max) {
my_type zero = 0;
assert(max > 0);
return (zero <= i && i <= max);
}
答案 2 :(得分: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
}
答案 3 :(得分: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
s should work for Clang too,因为它模仿GCC的语法。其他编译器可能具有类似的语法(例如Visual C/C++的#pragma warning( disable: ... )
),并且应该可以使用预处理器指令为每个编译器选择适当的#pragma
。
这种方法的主要缺点是审美 - 它用丑陋的#pragma
来填充代码。也就是说,我认为这样做更可取于故意模糊(并可能取消优化)您的代码,以避免编译器警告。至少,对于#pragma
,下一个程序员可以清楚地看到代码为什么会按原样编写代码。
为了使这个更干净,更便携,您可以使用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
和{{1}每个编译器包含适当的warnings_restore.h
,然后用你想要的代码括起来:
#pragma
答案 4 :(得分:2)
一个简单的解决方案是进行双面测试。
真正失去的唯一属性是适度的效率/性能。没有其他问题。
尽管OP已经考虑过这个问题,但任何解决方案都应该与此作为参考进行权衡。
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);
}