如何判断 C 语言的整型变量是有符号的?

8

作为一项练习,我想编写一个宏来告诉我一个整数变量是否带符号。这是我目前的代码,如果我在gcc -fsigned-char或-funsigned-char上尝试对char变量进行操作,我会得到预期的结果。

#define ISVARSIGNED(V) (V = -1, (V < 0) ? 1 : 0) 

这个是否是可移植的?有没有一种方法可以在不破坏变量值的情况下实现这一点?


这是一个有趣的问题,但我更加好奇的是您打算如何使用这些信息。有分享的机会吗? - Stephen Canon
2
@jeffamaphone:实际上,这就是C++中模板发挥作用的地方。 - sbi
1
@stephentyrone - 我认为这只是出于好奇心,基于OP问题的前三个单词。我无法想象这种信息的实际应用场景。 - Chris Lutz
@Chris @stephentyrone 这是来自Peter Van Der Linden的《Expert C Programming》一书中的练习。 - sigjuice
7个回答

6
#define ISVARSIGNED(V)  ((V)<0 || (-V)<0 || (V-1)<0)

第三个测试处理V == 0的情况,但不会改变V的值。

在我的编译器(gcc/cygwin)中,这适用于int和long,但不适用于char或short。

#define ISVARSIGNED(V) ((V)-1<0 || -(V)-1<0)

同时也能在两个测试中完成任务。


迄今为止我见过的最好的。可移植、符合标准,就我所知准确无误。 - David Thornley
2
在评估 < == > 表达式时,不区分有符号/无符号的 short 和 char 类型。这些类型会被提升为 int。 - mob
如果您想使其适用于 shortchar,您可以在使用变量之前将其转换为 char。这应该能够处理大多数溢出问题。我认为... - Chris Lutz

5
#define ISVARSIGNED(V) ((-(V) < 0) != ((V) < 0))

在不破坏变量值的情况下。但对于0值无效。

那么,这种情况怎么处理呢:

#define ISVARSIGNED(V) (((V)-(V)-1) < 0)

3
#define ISVARSIGNED(V) ((-V < 0) != (V < 0)) 意思是判断一个变量是否为有符号类型。 - plinth
是的,我不应该复制并粘贴那些多余的东西;-) - ypnos
@plinth,你忘记了在“V”周围加上“额外”的括号,这样可以避免疯狂的宏扩展。 - rmeador
1
当V == 0时会失败。此外,如果V具有最负可能的值,则很可能会失败,因为没有正等价物。 - David Thornley
#define ISVARSIGNED(V) ((-(V) < 0) != ((V) < 0)) 注意。有趣的是,这个测试在V == 0时失败。 - plinth
第二个版本适用于0、INT_MIN、UINT_MAX和INT_MAX在适当的类型中。 - plinth

5
如果您正在使用GCC,可以使用typeof关键字来避免覆盖该值:
#define ISVARSIGNED(V) ({ typeof (V) _V = -1; _V < 0 ? 1 : 0 })

这里创建了一个临时变量_V,它的类型与V相同。

至于可移植性,我不确定。它可以在补码机器上运行(也就是你的代码可能会在所有计算机上运行),我认为它也可以在反码和原码机器上运行。另外,如果您使用typeof,您可能需要将-1转换为typeof(V)以使其更安全(即更少出现警告)。


在C++中,无论整数表示方式如何(对于n位,值为2^n-1),它都是由标准保证可以工作的。我手头没有C标准(任何一个)。 - David Thornley
我也不确定,但我记得在维基百科上(所有真实事物的来源:P)读到C标准允许我列出的三种表示方法。虽然现在没有人再使用它们了... - Chris Lutz

2

这个简单的解决方案没有任何副作用,包括仅引用v一次(在宏中很重要)的好处。我们使用gcc扩展"typeof"来获取v的类型,然后将-1强制转换为该类型:

#define IS_SIGNED_TYPE(v)   ((typeof(v))-1 <= 0)

使用“<=”而不是“<”可以避免某些情况下(启用时)编译器警告。


1
一个不同的方法来回答所有“使其变为负面”的答案:
#define ISVARSIGNED(V) (~(V^V)<0)

这样就不需要为不同的 V 值设置特殊情况,因为 ∀ V ∈ ℤ,V^V = 0。


-1
有符号/无符号数学的一个特殊特点是,当你对有符号数字进行右移时,最高有效位被复制。而当你对无符号数字进行移位时,新的位都为0。
#define HIGH_BIT(n) ((n) & (1 << sizeof(n) * CHAR_BITS - 1))
#define IS_SIGNED(n) (HIGH_BIT(n) ? HIGH_BIT(n >> 1) != 0 : HIGH_BIT(~n >> 1) != 0

基本上,此宏使用条件表达式来确定数字的高位是否设置。如果没有设置,则通过按位取反该数字来设置它。我们不能进行算术否定,因为-0==0。然后,我们右移1位并测试是否发生符号扩展。

这假定2的补码算术,但通常是一种安全的假设。


你对位移操作的行为做出这些假设有依据吗? - Chris Lutz
@sbi 修改后使用CHAR_BITS。不过我很好奇:你在使用什么样的机器,它没有每个字节8位?它有多少位?我知道这样的系统很久以前存在,但我不知道现代系统中是否存在不具备8位字节的情况。 - Jay Conrod
即使在2的补码实现中,有符号右移也不能保证是算术右移。可以毫不犹豫地说,这个解决方案适用于大多数实现,在你遇到的所有实现中都适用等等。但是,如果标准打算指定右移时进行符号填充,则不必担心,它会明确说明的 :-) - Steve Jessop
@Jay:我正在使用PC进行工作,虽然多年来我的大部分代码已经移植到了许多平台,但我认为从来没有一个平台没有8位的char类型。 :) 这就是为什么我写下它更适合在PC上这样做的原因。但是,我怀疑在我还在编程时可能会引入16位字符类型。(难道.NET没有16位的char吗?) - sbi
1
@sbi:有各种带有16、24(!)或32位字符的DSP。 - Steve Jessop
显示剩余4条评论

-1

你到底为什么需要它成为一个宏?使用模板非常好:

template <typename T>
bool is_signed(T) {
    static_assert(std::numeric_limits<T>::is_specialized, "Specialize std::numeric_limits<T>");
    return std::numeric_limits<T>::is_signed;
}

这将适用于所有基本整数类型,开箱即用。但对于指针,在仅使用减法和比较的版本可能无法在编译时通过。

编辑:糟糕,问题要求使用C语言。不过,模板是一种好方法:P


一个 C 语言的整数,不是 C++ 的整数。 - Sapphire_Brick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接