C语言中检查有符号数的表示方式

5
以下是检查带符号数表示的方法。在我的机器上,这种方法能够正确地检查二进制补码,但我没有单独的反码或补码机器来进行测试。这段代码能否正常工作,并且更重要的是,它是否具有可移植性?
文件:platform.h
#ifndef PLATFORM_H
#define PLATFORM_H
#include <limits.h>

static
const union {
    signed char sc;
    unsigned char uc;
} plat_4xvYw = {.sc = -1};

#define IS_TWOS_COMPL (plat_4xvYw.uc == UCHAR_MAX)
#define IS_ONES_COMPL (plat_4xvYw.uc == UCHAR_MAX - 1)
#define IS_SIGNED_MAG (plat_4xvYw.uc == (1U << (CHAR_BIT - 1)) + 1U)

#endif

File: a.c

#include <inttypes.h>
#include <limits.h>
#include "platform.h"
#include <assert.h>

int
main (void) {

    assert (IS_TWOS_COMPL);
    if (IS_TWOS_COMPL) {

        printf ("twos complement\n");
    } else if (IS_ONES_COMPL) {

        printf ("ones complement\n");
    } else if (IS_SIGNED_MAG) {

        printf ("signed magnitude\n");
    }
    return 0;
}

3
要找到一台不使用二进制补码的计算机,你需要非常努力。我怀疑在过去的20-30年里,除了学术界以外,几乎没有任何一台计算机不使用二进制补码。 - Some programmer dude
我不是100%确定,但我认为将有符号整数存储并作为无符号整数访问是实现定义或未定义的行为。请参阅C99 6.5 §4有关按位运算符的内容:“这些运算符产生依赖于整数的内部表示的值,并且对于有符号类型具有实现定义和未定义的方面。” 就我所理解的,同样的情况也适用于此联合体。 - Lundin
@Lundin:你可以将它作为unsigned来访问(它是严格别名规则允许的类型之一),但除非int的值可以表示为unsigned(即非负数),否则不能保证它不是一个unsigned的陷阱值。因此,它并不是100%可移植的,但问题并不在于通常情况下访问有符号整数,而在于访问特定的-1 - Steve Jessop
@steve 我的印象是,char 类型不会有这样的问题,因为 C 保证不会有填充位。没有字节序问题。C 保证值位有一对一的映射。所以唯一的问题是符号位是否映射到无符号值位。鉴于有符号和无符号 char 必须具有相同的宽度,那就必须是这种情况。(话虽如此,我认为你的答案更好) - tyty
@steve,你说得对,signed char 可能有填充位。请参考 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1310.htm。目前已经有计划移除这个问题,详情请见 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1375.pdf。 - tyty
显示剩余2条评论
1个回答

5

我认为最好只是屏蔽负数的int位:

if ((-1 & 0x1) == 0) {
    // -1 ends in "0" => 1s' complement
} else if ((-1 & 0x2) == 0) {
    // -1 ends in "01" => sign-magnitude
} else {
    // -1 ends in "11" => two's complement
}

严格来说,这并不能告诉您与您的代码相同的内容,因为不能保证int和signed char使用相同的符号位含义。但是(a)严肃吗?(b)这适用于int和更大的类型,对于较小的类型则更加棘手。unsigned char保证没有填充位,但signed char不是。因此,我认为有可能出现(例如)CHAR_BIT == 9,UCHAR_MAX = 511,CHAR_MAX = 127,并且signed char有1个填充位。然后您的代码可能会失败:存储的有符号值中的符号位不一定在您期望的位置上,填充位的值可以是0或1。
在许多情况下,您可以在程序中使用int8_t而不是signed char。如果它存在,则保证是2的补码,因此可能使您不必关心signed char的表示。如果不存在,则程序将无法编译,这与您的断言相当。您将从具有2的补码但没有8位字符且因此不提供int8_t的平台获得虚假负面结果。这可能会或可能不会困扰您...

请澄清一下,您是在暗示有符号字符的负数表示可以采用2的补码,短整型可以采用另一种系统(例如1的补码),而整型等等... 另外,我不理解int和signed char使用相同的符号位含义的部分。 - tyty
@tyty:是的,我的意思是signed char可以是二进制补码,而short则是一进制补码。标准中6.2.6.2/2规定符号位以“以下方式之一”修改值,然后列出三种符号位影响值的方式,对应于三种允许的表示方法。因此,“符号位的含义”与“表示法”相同。它没有明确说明实现必须为每种整数类型选择相同的“方式”,因此我认为没有这样的要求。因此,使不同的选择看起来很奇怪但是合法的。 - Steve Jessop
请注意,如果存在 uint8_t,则必须使用二进制补码定义 unsigned char 或使用扩展整数类型来定义它。无论哪种方式,CHAR_BIT 必须为 8。 - R.. GitHub STOP HELPING ICE
@steve 注意这个措辞:“未指定行为,每个实现都要记录选择的方式”。另请参见http://www.open-std.org/jtc1/sc22/wg14/www/docs/n868.htm和http://www.open-std.org/jtc1/sc22/wg14/www/docs/n873.htm:搜索6.2.6.2。当时的措辞是:“实现应记录哪个适用”。这似乎很清楚,他们只打算使用1。此外,他们正在考虑删除1的补码和有符号的mag。因此,他们不太可能想到支持所有三种情况的机器。 - tyty
@chux:确实,我评论中的那部分是无意义的。我想它本应该是关于 int8_tsigned char 的。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论

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