检查未知签名的变量是否在间隔中

时间:2016-02-14 04:00:36

标签: c

在某些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,对吧?如果是这样,这应该工作。但是,我不确定这是否是一种可行的方法。例如,假设签名类型在所有平台上使用两个补码是安全的吗?有没有更好的方法来做到这一点?

5 个答案:

答案 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);
}