我在论坛中遇到过这个问题。答案是这样的:
#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
?第二个解决方案是什么?我不明白这句话:“论证是一种类型”。更重要的是,作者指出,第一个#define
在ANSI C中不起作用(但在K& R C中有效)。为什么不呢?
答案 0 :(得分:8)
#define ISUNSIGNED(a) (a >= 0 && ~a >= 0)
对于带正号的有符号值,a >= 0
将为真(显然),~a >= 0
将为假,因为我们已经翻转了位,因此现在设置了符号位,从而产生负值值。因此整个表达都是错误的。
对于带符号的否定值,a >= 0
将为false(显然),并且不会计算表达式的其余部分;表达式的整体结果为false。
对于无符号值,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)
表达式中已签名的1
和0
文字将提升为unsigned
以匹配第一个0
,因此整个表达式将被评估为无符号比较。无符号算术中的0 - 1
将包围,导致最大可能无符号值(所有位设置为1),大于0,因此结果为真。
至于为什么它适用于K& R C,而不是ANSI C,也许this article可以解释一下:
当加宽unsigned char或unsigned short时,结果类型为 int如果int足够大以表示所有的值 较小的类型。否则,结果类型为unsigned int。价值 保留规则为大多数人产生最少的惊喜算术结果 表达式。
我想这意味着在比较unsigned short
到0
时,无符号值会转换为signed int
,从而破坏宏的行为。
您可以通过让(a-a)
根据需要评估为有符号或无符号零而不是始终签名的文字0
来解决此问题。
答案 1 :(得分:3)
对于第一个宏:如果一个值为正(>= 0
),则其在2补码中的按位否定必须为负,如果它是有问题的。无符号值将保持正值:
~64 == -65 (signed)
~64 == 191 (unsigneD)
对于第二种情况:如果将类型传递给marco,它会检查此类型是否可以包含烧结值。对于有问题的类型0-1 == -1
,对于无符号类型,它是一个正值:
(char) 0-1 == -1
(unsigned char) 0-1 == 255
答案 2 :(得分:3)
对~a >= 0
的检查依赖于翻转已签名的位。逻辑如下:
a >= 0
返回false。~ a
将导致最大无符号值 - a,仍为正无符号数。事实上,我认为a >= 0 && ~a >= 0
并且数字仍然可以签名。这是因为可能存在负零。特别是,对于一个补码,全零的值表示0
,而表示否定(全1)将为负0(或陷阱表示)。
还有整数促销的问题:
如果int可以表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些被称为整数促销。所有其他类型都不会被整数促销更改。
整数提升适用于“整数转换等级小于或等于int和unsigned int等级的整数类型的对象或表达式”和“_Bool,int,signed int类型的[a]位字段”或unsigned 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));
}
答案 3 :(得分:2)
-1
转换为该类型,是否小于0
,如下所示:
#define issigned(t) (((t)(-1)) < 0)
如果您想了解特定的变量,可以向编译器询问该变量的类型:
issigned(typeof(v))
答案 4 :(得分:1)
问题中提出的答案(a >= 0 && ~a >= 0)
是错误的,因为它取决于整数表示,特别是它不适用于那些&#39;如果a
为0,则为补码。这是一种正确,便携的做事方式。
首先,如果T
是整数类型,则可以使用以下宏:
#define ISUNSIGNED(T) ((T) -1 > 0)
因此,对于具有值作为参数的版本,如果值a
的类型至少为int
(以避免整数提升,这可能会更改类型的签名) ,可以使用以下宏:
#define ISUNSIGNED(a) (0*(a) - 1 > 0)
它不依赖于整数的表示,因此它与ISO C标准允许的所有整数表示兼容(两个补码,一个补码和符号幅度)。
答案 5 :(得分:0)
第一个#define适用于变量名称。第二个#define适用于类型名称。 (显然,如果你打算在同一个程序中使用它们,你必须给它们不同的名字。)
我不会随便知道为什么要么不能使用ANSI C。