【问题标题】:value vs type: Code to Determine if a Variable Is Signed or Not值与类型:确定变量是否签名的代码
【发布时间】:2011-11-20 03:49:34
【问题描述】:

我在论坛中遇到了这个问题。答案是这样的:

#define ISUNSIGNED(a) (a >= 0 && ~a >= 0) 

//Alternatively, assuming the argument is to be a type, one answer would use type casts: 

#define ISUNSIGNED(type) ((type)0 - 1 > 0)

我对此有几个问题。为什么我们需要检查~a >= 0?第二种解决方案是什么?我不明白这句话:“argument is to be a type”。更重要的是,作者指出第一个 #define 在 ANSI C 中不起作用(但在 K&R C 中起作用)。为什么不呢?

【问题讨论】:

  • 关于第一部分:带符号的值也可以是正数。因此仅测试a >= 0 是不够的。
  • C 类型是根据而不是表示定义的。唯一的例外是无符号整数值,您可以假设特定的二进制表示是您一直认为的那样。因此,您应该只对无符号整数类型使用按位运算。其他一切都会给你不可预测的(实现定义的或未定义的)结果。
  • 刚刚编辑了我的答案,包括对在 ANSI C 中不起作用的新促销规则的解释。
  • @Kerrek:不是这样。签名类型只有 3 种法律表示。只要您检查基于位运算的解决方案对于 3 种可能的表示形式是否正确,它就是 100% 可移植的。不过要注意的一件事是,在没有负零的非二进制补码系统上,~0 可能是未定义的行为。
  • @maxpayne:应该注意的是,这两个宏都不是测试表达式/类型是否签名的糟糕方法。有更好的方法可以做到这一点,例如(1?-1:(a))<0。另请注意,由于整数提升问题,所有这些方法对于小于 int 的类型都将失败。

标签: c unsigned signed


【解决方案1】:
#define ISUNSIGNED(a) (a >= 0 && ~a >= 0) 

对于正数的有符号值,a >= 0 将为真(显然)而~a >= 0 将为假,因为我们已经翻转了位,因此现在设置了符号位,从而产生负值。因此整个表达式为假。

对于一个负数的有符号值,a >= 0 将是假的(很明显)并且表达式的其余部分不会被计算;表达式的总体结果为假。

对于无符号值,a >= 0始终为真(显然,因为无符号值不能为负)。如果我们翻转这些位,那么~a >= 0 也是如此,因为即使最高有效位(符号位)设置为 1,它仍然被视为正值。

因此,如果原始值及其按位倒数都是正数,即它是无符号值,则表达式返回 true。

#define ISUNSIGNED(type) ((type)0 - 1 > 0)

这将使用类型而不是值来调用:例如 ISUNSIGNED(int)ISUNSIGNED(unsigned int)

对于int,代码扩展为

((int)0 - 1 > 0)  

这是错误的,因为-1 不大于0

对于unsigned int,代码扩展为

((unsigned int)0 - 1 > 0) 

表达式中带符号的10 文字被提升为unsigned 以匹配第一个0,因此整个表达式被评估为无符号比较。 0 - 1 在无符号算术中会回绕,产生最大可能的无符号值(所有位都设置为 1),大于 0,因此结果为真。

至于为什么它可以与 K&R C 而不是 ANSI C 一起工作,也许this article 可以提供一些启示:

当 unsigned char 或 unsigned short 被加宽时,结果类型为 int 如果一个 int 大到足以表示 较小的类型。否则,结果类型为 unsigned int。价值 对于大多数人来说,保留规则会产生最不意外的算术结果 表达式。

我猜这意味着当比较 unsigned short0 时,例如,无符号值将转换为 signed int,这会破坏宏的行为。

您可以通过使用 (a-a) 来解决这个问题,它的计算结果是适当的有符号或无符号零,而不是总是有符号的文字 0

【讨论】:

  • 感谢您的回答。你能告诉我我能做些什么来防止@Artefacto的回答所指出的整数促销吗?即如何在 ANSI C 中编写宏?
  • 所以宏变成了#define ISUNSIGNED(a) (a >= a-a && ~a >= a-a) ?
  • 是的,但是为了可读性和安全性,将 (a-a) 括在括号中!
  • 我不知道您为什么认为将 a-a 更改为 0 会有所不同。整数提升也适用于操作数二元运算符-,即使它没有并且(unsigned char)a - (unsigned char)a 实际上有unsigned char 类型(它没有,它已经有int 类型),提升仍然会发生何时评估 >= 运算符。
  • (a >= 0 && ~a >= 0) 如果 a 是有符号类型的 0,则在一个补码中给出 true 而不是 false。为了支持一个人的补充,似乎以下方法会起作用:((a >= 0 && ~a >= 0) || (~0 == 0 && ~(0*a))).
【解决方案2】:

对于第一个宏:如果一个值是正数 (>= 0),它的按位否定在 2-补码中必须是负数,如果它是 singed。无符号值将保持为正数:

~64 == -65 (signed)
~64 == 191 (unsigneD)

第二个:如果你将一个类型传递给 marco,它会检查这个类型是否可以保存单值。对于singed 类型,0-1 == -1,对于unsigned,它是一个正值:

(char) 0-1 == -1
(unsigned char) 0-1 == 255

【讨论】:

    【解决方案3】:

    ~a >= 0 的检查依赖于翻转有符号位。逻辑如下:

    • 如果数字为负数,则不能无符号,所以a >= 0返回false。
    • 如果是正数,它要么是有符号的,要么是无符号的。
    • 如果这是无符号的,应用~ a 将产生最大的无符号值 - a,仍然是一个正的无符号数。
    • 如果有符号,符号位将被取反,产生一个负值。

    事实上,我认为a >= 0 && ~a >= 0 并且仍然是数字签名可能是真的。这是因为可能存在负零。特别是,对于一个补码,一个全为零的值表示0,而取反的表示(全为 1)将是负 0(或陷阱表示)。

    还有整数提升的问题:

    如果一个int可以表示原始类型的所有值,则将该值转换为一个int;否则,它将转换为无符号整数。这些被称为整数促销。整数提升不会改变所有其他类型。

    整数提升适用于“具有整数类型的对象或表达式,其整数转换等级小于或等于 int 和 unsigned int 的等级”和“[a] _Bool、int、signed int 类型的位字段,或无符号整数。”它们与一元运算符 ~ 和“通常的算术转换”一起使用,其中包含第一次比较,因此它们适用于此。

    因此unsigned char 将被提升为int,因此在最初未签名时已签名。

    例如,这将给出错误的结果:

    #include<stdio.h>
    #define ISUNSIGNED(a) (a >= 0 && ~a >= 0)
    void main(void) {
        unsigned char uc = 8;
        printf("%d", ISUNSIGNED(uc));
    }
    

    【讨论】:

    • 不错的答案。如何在 ANSI C 中纠正这个问题?即如何修改宏使其不受整数提升的影响?
    【解决方案4】:

    位翻转的技巧很可爱,但如果您想知道某事物的符号性,还有更直接的方法可以做到。签名是类型的属性,而不是值。要确定变量是否已签名,您可以询问编译器 -1(转换为该类型)是否小于 0,如下所示:

    #define issigned(t) (((t)(-1)) < 0)
    

    如果您想知道特定变量的这一点,可以向编译器询问该变量的类型:

    issigned(typeof(v))
    

    【讨论】:

    • 除了 typeof 不是标准的,至少目前不是(在 C11 中)。例如,尝试使用gcc -std=c11
    【解决方案5】:

    问题中提出的答案(a &gt;= 0 &amp;&amp; ~a &gt;= 0) 很糟糕,因为它取决于整数表示,特别是如果a 是带符号类型的0,它在反码中不起作用。这是一种正确的、可移植的做事方式。

    首先,如果T是整数类型,可以使用如下宏:

    #define ISUNSIGNED(T) ((T) -1 > 0)
    

    因此,对于有值作为参数的版本,如果值a的类型至少是int(避免整数提升,可能会改变类型的符号),下面的宏可以使用:

    #define ISUNSIGNED(a) (0*(a) - 1 > 0)
    

    它不依赖于整数的表示,因此它与 ISO C 标准允许的所有整数表示(二进制补码、二进制补码和符号幅度)兼容。

    【讨论】:

      【解决方案6】:

      第一个#define 作用于变量名。第二个#define 作用于类型名称。 (显然,如果您打算在同一个程序中同时使用两者,则必须给它们起不同的名称。)

      我不知道为什么两者都不能与 ANSI C 一起使用。

      【讨论】:

      • 作者提到了一些关于“新的推广规则”
      • 我很确定我曾经(或两次)在 C++ 中做过类似于 #1 的事情。不过,我不记得我使用的确切表达方式了。
      猜你喜欢
      • 2020-01-01
      • 2010-12-11
      • 2010-11-22
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 2017-01-06
      • 2016-03-30
      • 2011-02-08
      相关资源
      最近更新 更多