是否使用gcc在内部使用相同的位表示int和char?

时间:2012-03-18 10:28:46

标签: c gcc char int

我正在玩unicode字符(不使用wchar_t支持)只是为了好玩。我只使用常规字符数据类型。我注意到,当它们以十六进制打印时,它们显示的是完整的4个字节而不是仅仅一个字节。

对于前。考虑这个c文件:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *s = (char *) malloc(100);
    fgets(s, 100, stdin);
    while (s && *s != '\0') {
            printf("%x\n", *s);
            s++;
    }
    return 0;
}

用gcc编译并输入'cent'符号(hex:c2 a2)后,我得到以下输出

$ ./a.out
¢
ffffffc2: ?
ffffffa2: ?
a: 

因此,不仅仅打印c2和a2,我得到了整个4个字节,就像它是一个int类型一样。

这是否意味着char的长度不是1字节,ascii使它看起来像1字节?

3 个答案:

答案 0 :(得分:5)

也许上面三个字节变成0xFFFFFF的原因需要更多解释?

由于符号扩展,为* s打印的值的高三个字节的值为0xFF。

传递给printf的char值在调用int之前会扩展为printf

这是由于C的默认行为。

如果没有signedunsigned,编译器可以默认将char解释为signed charunsigned char。除非使用命令行选项或编译指示明确更改,否则它始终是一个或另一个。在这种情况下,我们可以看到它是signed char

在没有更多信息(原型或演员表)的情况下,C传递:

  • int,因此charshortunsigned char unsigned short会转换为int。它永远不会传递char,unsigned char,signed char,作为单个字节,它总是传递int
  • unsigned intint的大小相同,因此值不会更改

编译器需要决定如何将较小的值转换为int

  • signed值:int的高位字节是从较小的值扩展的符号,有效地复制顶部符号位向上以填充int。如果较小的有符号值的最高位为0,则高位字节用0填充。如果较小的有符号值的最高位为1,则高位字节用1填充。因此printf(“%x”,* s )打印ffffffc2
  • unsigned值未进行符号扩展,int的高位字节为“零填充”

因此,C可以在没有原型的情况下调用函数(尽管编译器通常会对此进行警告)

所以你可以写,并期望这个运行(虽然我希望你的编译器发出警告):

/* Notice the include is 'removed' so the C compiler does default behaviour */
/* #include <stdio.h> */

int main (int argc, const char * argv[]) {
    signed char schar[] = "\x70\x80";
    unsigned char uchar[] = "\x70\x80";

    printf("schar[0]=%x schar[1]=%x uchar[0]=%x uchar[1]=%x\n", 
            schar[0],   schar[1],   uchar[0],   uchar[1]);
    return 0;
}

打印:

schar[0]=70 schar[1]=ffffff80 uchar[0]=70 uchar[1]=80

我的(Mac的gcc)编译器将char值解释为signed char,因此编译器生成代码以将char扩展为int printf调用之前。

如果签名的char值设置了顶部(符号)位(\ x80),则转换为int符号会扩展char值。符号扩展用1填充高位字节(在这种情况下是3个字节以产生4个字节int),由printf打印为ffffff80

如果签名的char值的顶部(符号)位清除(\ x70),则转换为int仍然符号会扩展char值。在这种情况下,符号为0,因此符号扩展名用0填充高位字节,由printf打印为70

我的示例显示了值为unsigned char的情况。在这两种情况下,值不会符号扩展,因为值为unsigned。相反,它们使用0填充扩展为int。它可能看起来像printf只打印一个字节,因为该值的相邻三个字节将为0.但它打印整个int,它发生的值是0x00000070和0x00000080,因为unsigned char价值转换为 int没有符号扩展名。

您可以强制printf仅使用合适的格式(%hhx)打印int的低字节,因此这只会正确打印原始字符中的值:

/* Notice the include is 'removed' so the C compiler does default behaviour */
/* #include <stdio.h> */

int main (int argc, const char * argv[]) {
    char schar[] = "\x70\x80";
    unsigned char uchar[] = "\x70\x80";

    printf("schar[0]=%hhx schar[1]=%hhx uchar[0]=%hhx uchar[1]=%hhx\n", 
           schar[0],   schar[1],   uchar[0],   uchar[1]);
    return 0;
}

打印:

schar[0]=70 schar[1]=80 uchar[0]=70 uchar[1]=80

因为printf解释%hhx将int视为unsigned char。这并没有改变在调用printf之前将char符号扩展为int的事实。它只是一种告诉printf如何解释int内容的方法。

在某种程度上,对于signed char *schar%hhx的含义看起来有些误导,但'%x'格式无论如何都将int解释为unsigned,并且我的printf)没有格式打印十六进制用于签名值(恕我直言,这将是一个令人困惑的)。

可悲的是,ISO / ANSI / ...没有自由发布我们的编程语言标准,所以我不能指出规范,但搜索网络可能会出现工作草案。我没有试过找到它们。我推荐Samuel P. Harbison和Guy L. Steele撰写的“C:A参考手册”作为ISO文件的更便宜的替代品。

HTH

答案 1 :(得分:4)

没有。 printf是一个变量参数函数,变量参数函数的参数将被提升为int。在这种情况下,char是负数,因此它会被符号扩展。

答案 2 :(得分:1)

%x告诉printf要打印的值是无符号int。因此,它会将char提升为unsigned int,根据需要进行扩展,然后打印出结果值。