在C89中,将无符号字符转换为有符号字符,或者将有符号字符转换为无符号字符是否定义良好?

5
注意:建议的重复问题处理的是unsigned intsigned int,而不是unsigned charsigned char。建议的重复问题涉及C11。这个问题只涉及C89。这个问题可以重新打开吗?
我的代码:
#include <stdio.h>

int main()
{
    signed char c;
    unsigned char d;

    c = (signed char) -2;
    d = (unsigned char) c;
    printf("%d %d\n", c, d);

    d = (unsigned char) 254;
    c = (signed char) d;
    printf("%d %d\n", c, d);

    return 0;
}

输出:

$ clang -Wall -Wextra -pedantic -std=c89 foo.c && ./a.out
-2 254
-2 254

在符合C89标准的编译器中,对于上述两种转换,输出是否保证为"-2 254"?还是输出取决于具体的实现方式?

1
只有在unsigned值在signed的正数范围内时,才能明确定义。因此,如果char是8位,那么只有当值介于0和127之间时,才能明确定义。 - Barmar
2
printf在UCHAR_MAX > INT_MAX的系统上可能会有问题,尽管这是另一个完全不同的帖子。 - M.M
2
printf 在 UCHAR_MAX > INT_MAX 的系统上可能会有问题,尽管这是另一个完全不同的帖子。 - M.M
4
C从未说过CHAR_BIT等于8。在具有更宽char的平台上,即使所有规则都被定义,-2也不会是254。 - phuclv
4
C从未说过CHAR_BIT等于8。在具有更宽的char的平台上,即使所有规则都被定义,-2也不会是254。 - phuclv
显示剩余47条评论
3个回答

4
在C89中,从unsigned char转换为signed char以及相反的转换是否定义良好?
将unsigned类型转换为signed类型是定义良好的。而将signed类型转换为unsigned类型则取决于具体实现。
在符合标准的C89编译器中,对于上述两种转换,输出是否保证为-2 254?
不保证。
或者输出是否依赖于具体实现?
是的。
不是所有的实现都使用8位的char,而且将其转换为有符号类型会涉及到具体的实现细节。
规范细节:C89 Conversions。这个措辞与最近的C规范有所不同。我没有发现显著的差异。
当时,代码可以使用以下内容,并让编译器生成优化的、定义明确的代码。
c = (signed char) (d > SCHAR_MAX ? d - UCHAR_MAX - 1 : d);

可能需要更多的思考来涵盖所有情况。

1
FYI,将整数类型转换为无符号整数类型确实是明确定义的(除了目标类型的宽度可能是实现定义的)。然而,将浮点数转换为无符号整数类型对于超出范围的值是未定义的。 - Eric Postpischil
1
FYI,将整数类型转换为无符号整数类型确实是明确定义的(除了目标类型的宽度可能是实现定义的)。然而,将浮点数转换为无符号整数类型对于超出范围的值是未定义的。 - Eric Postpischil
1
FYI,整数类型转换为无符号整数类型确实是明确定义的(除了目标类型的宽度可能是实现定义的)。然而,将浮点数转换为无符号整数类型对于超出范围的值是未定义的。 - undefined

-2
标准的作者几乎可以肯定地预期,一个实现应该以这样一种方式实现有符号和无符号字符类型之间的转换,即在任何没有强制要求以其他方式处理它们的实现上,往返转换应该是保持值不变的。他们几乎可以确定这样的实现如果存在的话,应该是相当罕见的。因此,委员会无需担心那些有充分理由以非常规方式处理此类转换的实现是否应被要求以保持值不变的方式处理它们。如果没有任何实现真的有理由偏离常见行为,那么对于标准是否规定了普遍的处理方式,没有人应该在意;而如果一个实现确实有充分理由偏离,那么与其判断此类偏离的利弊,使用它的人比委员会更有资格。

-3
如果我说错了什么,请纠正我。
你的问题有一个带有"未定义行为"的标志。我认为这是不正确的。
如果你对程序有任何疑问,我建议查看程序的反汇编代码。通过检查它,你所有的困惑可能会很容易地解决。
输出:
-2 254
-2 254

这是正确的,这是一种确定的行为。这种行为是由C语言本身或C语言标准决定的。
输出的关键取决于程序员如何解释存储的值FE。如果将FF视为无符号字符,则为255(或将FFFF视为无符号短整型,则为65535,或将FFFFFFFF视为无符号整型,则为4294967295)。如果将FF视为有符号字符,则为-1(或将FFFF视为有符号短整型,则为-1,或将FFFFFFFF视为有符号整型,则为-1)。
同样,如果将FE视为无符号字符,则为254。如果将FE视为有符号字符,则为-2。依此类推......
当你要求计算机存储"-2"和"254"时,计算机并不识别正数或负数,它只识别"0"(在电路中,可以说是"断开"或"损坏")和"1"(在电路中,可以说是"闭合"或"连接")。如果你要求计算机存储"-2",它会在内存中的某个位置存储"FE"(因为变量c和变量d的类型是char,占用1个字节)。正如@David C. Rankin指出的,在将负数编码为二进制补码的计算机上。同样地,如果你要求计算机存储"254",它也会在内存中的某个位置存储"FE"。
请参考下面的代码:
#include <stdio.h>

int main()
{
    signed char c;
    unsigned char d;

    c = (signed char) 0xFE;
    d = (unsigned char) c;
    printf("%d %d\n", c, d);

    d = (unsigned char)0xFE;
    c = (signed char) d;
    printf("%d %d\n", c, d);

    return 0;
}

使用以下命令运行它:
clang -Wall -Wextra -pedantic -std=c89 foo.c && ./a.out

将输出:
-2 254
-2 254

为什么输出是双数“-2 254”?
代码中没有出现“-2”和“254”。
似乎只观察到了数字“0xFF”。
c = (signed char) 0xFE;

d = (unsigned char)0xFE;

那么,-2254是从哪里来的?

简单解释:(下面有更详细的解释)

enter image description here

我们发现变量c变量dchar类型,但%d输出的是int(或有符号整数),编译器应该如何处理?答案是有符号扩展和无符号扩展
所以现在存储在变量c中的值0xFE通过符号扩展转换为0xFFFFFFFE,而存储在变量d中的值0xFE通过零扩展转换为0x000000FE。当使用%d打印0xFFFFFFFE时,输出结果为-2,而使用%d打印0x000000FE时,输出结果为254。(对于0xFFFFFFFE你可能不太熟悉或不太理解吗?请继续阅读,下面有解释。)
或者像下面这样的代码:
#include <stdio.h>

int main()
{
    signed char c;
    unsigned char d;

    c = (signed char) 254;
    d = (unsigned char) c;
    printf("%d %d\n", c, d);

    d = (unsigned char)254;
    c = (signed char) d;
    printf("%d %d\n", c, d);

    return 0;
}

使用以下命令运行它:
clang -Wall -Wextra -pedantic -std=c89 foo.c && ./a.out

将输出:
-2 254
-2 254

为了更好地解释您的困惑,请看一下以下代码。
#include <stdio.h>

int main()
{
    signed char c;
    unsigned char d;

    c = (signed char) -2;
    d = (unsigned char) c;
    printf("%d %d %u %u\n", c, d, c, d);

    d = (unsigned char) 254;
    c = (signed char) d;
    printf("%d %d %u %u\n", c, d, c, d);

    return 0;
}

使用以下命令运行它:
clang -Wall -Wextra -pedantic -std=c89 foo.c && ./a.out

将输出:
-2 254 4294967294 254
-2 254 4294967294 254

或者使用以下命令运行它:
gcc -g -o foo foo.c && ./foo

将输出:
-2 254 4294967294 254
-2 254 4294967294 254

输出是正确的。
更多详细解释:

enter image description here

我们发现变量c或变量d是char类型,但是%u输出的是unsigned int类型,编译器应该如何处理?答案是进行有符号扩展和无符号扩展。
当我们检查反汇编代码时,确实发现了符号扩展和零扩展。请参考下面的图片。

enter image description here

另一张图片:

enter image description here

我们发现在给变量c和变量d赋值时使用了char类型(BYTE),但在printf之前,变量c和变量d的值之前有一些指令。
movzx  esi,BYTE PTR [rbp-0x1]
movsx  ecx,BYTE PTR [rbp-0x2]
movzx  edx,BYTE PTR [rbp-0x1]
movsx  eax,BYTE PTR [rbp-0x2]

movzx是零扩展,而movsx是符号扩展。就像esiecxedxeax等于intecx占据4个字节,int类型也占据4个字节)。

所以现在存储在变量c中的值0xFE通过符号扩展被转换为0xFFFFFFFE(保存在ecx或eax中),而存储在变量d中的值0xFE通过零扩展被转换为0x000000FE(保存在esi或edx中)。当使用%u打印0xFFFFFFFE时,结果为4294967294;当使用%d打印0xFFFFFFFE时,结果为-2;当使用%u打印0x000000FE时,结果为254;当使用%d打印0x000000FE时,结果为254。
4294967294的表示如下图所示。

enter image description here

-2的表示如下图所示。

enter image description here

所以现在你看到,当输出变量c变量d的值时,使用%d和%u来打印它们会得到不同的结果。然而,这两种表示都指向存储在内存中的同一个值。关键是你选择如何解释c或d的值。

3
只有来自实际标准的引用才能回答这个问题。其他像代码片段和图片之类的东西可以作为很好的说明补充,但如果没有相关的引用,它们就毫无意义。 - n. m. will see y&#39;all on Reddit
3
只有来自实际标准的引用才能回答这个问题。其他像代码片段和图片之类的东西可以作为很好的说明性补充,但如果没有相关的引用,它们就毫无意义。 - n. m. could be an AI
3
@Whozcry C89可能在OP的编译器和机器上产生所期望的输出,但是-2 254的输出并没有在所有符合C89标准的编译器和机器上指定。 - chux - Reinstate Monica
3
@Whozcry C89在OP的编译器和机器上可能会产生期望的输出,但"-2 254"的输出对于所有符合C89标准的编译器和所有机器都没有明确规定。 - chux - Reinstate Monica
3
@Whozcry C89 可能在 OP 的编译器和 OP 的机器上产生期望的输出,但对于所有兼容的 C89 编译器和所有机器,"-2 254" 输出并未指定。 - undefined
显示剩余110条评论

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