【问题标题】:Check whether variable of unknown signedness is in interval检查符号未知的变量是否在区间内
【发布时间】:2016-05-25 02:44:18
【问题描述】:

在某些 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 &gt;= 0 总是正确的,编译器会(正确地)警告它,我想避免这种情况。 (我不想关闭该警告,因为当这种比较实际上是无意的时它很有用,并且忽略编译器警告不是一个好主意。)

我目前的想法是将i 转换为无符号类型,然后只检查上限:

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

如果有符号类型具有二进制补码表示,则任何负值都应该大于max,一旦转换为无符号版本,对吗?如果是这样,这应该工作。但是,我不确定这是否是一种可取的方法。例如,假设签名类型在所有平台上都使用二进制补码是否安全?有没有更好的方法来做到这一点?

【问题讨论】:

  • 当您的签名测试值(解释为unsigned)可能大于(无符号)MAX/2 时,这将不起作用。可以这样吗? (嗯。也许我的意思是小于。)
  • 我很确定可以保证转换为兼容的无符号类型的负值将大于原始类型的任何正值。然而,如果你这样做,你会让人们抱怨“奇怪的代码”。
  • @Jongware 对不起,我不太明白你的意思。 MAX 不是类型的限制(例如,INT_MAXUINT_MAX)。使用大写字母具有误导性(现在已更改)。
  • @JohnColeman 我对忽略编译器警告没有任何宗教厌恶。但是,我认为应该没有是一个很好的经验法则。否则,必须跟踪警告正常和不正常的文件和行号。在这种情况下,错过真正问题的风险似乎很高。
  • A C 实现可以具有比size_t '更宽'(非正式地,更大)的整数类型,如果是这样,则强制转换可能会更改值并给您错误的结果。来自&lt;stdint.h&gt;uintmax_t 可移植地避免这种情况(在C99 及更高版本中,但您说的是C99)。 OTOH 2sC 不是问题;在 C 中将有符号整数转换为无符号定义即使在(非常非常罕见的)1sC 或 S&M 机器上也可以有效地将 w 加或减 2。

标签: c


【解决方案1】:

也许这是另一种直接的方法:

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
}

【讨论】:

  • 不会if ((my_type)-1 &gt; 0) 创建警告,因为它始终为真或假。很像 OP 的“总是正确的,编译器会(正确地)警告”?
  • 嗯,clang 确实给了我一个警告,但它在 return (0 &lt;= i &amp;&amp; i &lt;= max); // my_type is signed 上,说 comparison of 0 &lt;= unsigned expression is always true
【解决方案2】:

检查 0 和 max。但请避免直接进行 &gt;= 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;
}

我也会考虑更多。

【讨论】:

  • 谢谢!您能否详细说明“铸造是一个问题,因为它可能会以某种实现定义的方式缩小价值”?不太明白你的意思。
  • @Fredrik Savje 将long long(可能是my_type 的类型)转换为unsigned 定义明确,但可能会丢失一些范围,然后((size_t) i &lt;= (size_t) max) 就毫无意义,因为演员可能有更改了 max 值,但未更改 i
  • @sh1 该方法的 2 个问题: 强制转换的负数 i 可能会也可能不会超过强制转换的 max - 应该是 &gt;,它假定某种整数布局,虽然很常见,不是由 C 指定的。系统确实以一种狡猾的方式支持超过 uintmax_t 的整数类型。 (例如 128 位类型而不叫它们“整数”)虽然我怀疑你的想法会在 99.999% 的时间里奏效。
  • @Fredrik Savje 我看到了 4 种方法:预处理器、隐藏 >= 在代码中(如上)、_Generic()、二级函数 is_in_interval2(i, 0, max)。预处理器不理解my_type,因为它只使用intmax_t。用后续点隐藏 >=。 (Pedantic,volatile my_type j = i; if (j &gt; 0) return true; return j == 0;_Generic() 会很好用,除非很难以可移植的方式枚举所有有符号/无符号类型。
  • @chux,这个问题不仅存在于非标准的int 类型,还存在于floatdouble。我认为UINTMAX_MAX 小于两倍INTMAX_MAX 要求uintmax_t 有一个垃圾位。我承认这对于任何类型都是可能的,但对于 16 位类型 (DSP) 来说最合理。
【解决方案3】:

我认为这应该关闭编译器:

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

【讨论】:

  • 它会关闭一些编译器,但不是全部。编译器在针对声明的 const 变量进行测试是否会触发诸如“条件始终为真”或“条件始终为假”之类的警告方面有所不同。
  • @Peter 我明白了,谢谢!然后它变得不那么有吸引力了。
  • 我可以建议volatile 而不是const,但最好找到特定编译器的内联注释来禁用警告。
  • 或许volatile signed char zero = 0;?
  • @chux,我以前在这种情况下使用过volatile,但特别是为了说服调试器取消优化代码,以便我可以在调试器中编辑值。所以我知道,至少在某些编译器中,这意味着性能损失。
【解决方案4】:

如果您的编译器支持它,我相信最简洁的解决方案是保持代码不变,但使用#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
}

显然,与#pragmas should work for Clang too 完全相同,因为它模仿了 GCC 的语法。其他编译器可能具有类似的语法(例如,#pragma warning( disable: ... ) 用于 Visual C/C++),并且应该可以使用预处理器指令为每个编译器选择适当的 #pragmas。

这种方法的主要缺点是美观——它用丑陋的#pragmas 乱扔代码。也就是说,我认为这比故意混淆(并可能取消优化)代码更可取,以避免编译器警告。至少,有了#pragmas,下一个阅读代码的程序员就清楚为什么它是这样写的了。


为了使这个更简洁和更便携,您可以使用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.hwarnings_restore.h,其中包含为每个编译器提供适当的#pragma,然后将要消除警告的代码括起来:

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

【讨论】:

  • 我喜欢这个想法,但也看到了可移植性的一大缺点:有很多非 gcc C 编译器(和程序员),可能包括一个 OP,需要重新编写/@ 987654339@代码。
  • 谢谢!从某种意义上说,这可能是“正确”的做法。到目前为止提出的所有解决方案基本上都是欺骗编译器不警告它应该警告的东西的方法——显而易见的解决方案是暂时禁用警告。这里的问题是我正在编写一个小型 C 库,所以我不能依赖特定的编译器。
【解决方案5】:

一个简单的解决方案是进行双面测试。

真正失去的唯一属性是适度的效率/性能。没有其他问题。

虽然 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2018-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多