使用%u读取签名的字符

Deb*_*ish 8 c c++ pointers char pointer-arithmetic

#include <stdio.h>

int main() {
    int i,n;
    int a = 123456789;

    void *v = &a;

    unsigned char *c = (unsigned char*)v;

    for(i=0;i< sizeof a;i++) {
        printf("%u  ",*(c+i));
    }

    char *cc = (char*)v;
    printf("\n %d", *(cc+1));

    char *ccc = (char*)v;
    printf("\n %u \n", *(ccc+1));

}
Run Code Online (Sandbox Code Playgroud)

该程序在我的32位Ubuntu机器上生成以下输出.

21  205  91  7  
-51
4294967245
Run Code Online (Sandbox Code Playgroud)

前两行输出我能理解=>

  • 第1行:在内存中存储字节的顺序.
  • 第二行:第二个字节值的有符号值(2的补码).
  • 第3行:为什么这么大的价值?

请解释最后一行输出.为什么添加三个1的字节,因为(11111111111111111111111111001101) = 4294967245.

Lun*_*din 12

显然你的编译器使用有符号字符,它是一个小端,二进制补码系统.

123456789d = 075BCD15h
Little endian: 15 CD 5B 07
Run Code Online (Sandbox Code Playgroud)

因此v + 1给出了价值0xCD.当它存储在signed char中时,您将获得带-51符号的十进制格式.

传递给printf时,*(ccc+1)包含值的字符-51首先被隐式类型提升为int,因为像printf这样的可变参数函数有一条规则,表明所有小整数参数都会被提升为int(默认参数提升).在此促销期间,标志将被保留.你仍然有值-51,但对于32位有符号整数,这给出了值0xFFFFFFCD.

最后,%u说明符告诉printf将其视为无符号整数,因此最终得到4.29 bil.

这里要理解的重要部分是%u与实际类型提升无关,它只是告诉printf如何在提升解释数据.


Moh*_*ain 8

-51存储在8位十六进制是0xCD.(假设2s恭维二进制系统)

当你将它传递给类变量函数时printf,默认参数提升发生并char被提升为int表示0xFFFFFFCD(对于4字节int).

0xFFFFFFCD解释为int-51并将其解释为unsigned int4294967245.

进一步阅读:C函数调用中的默认参数提升


请解释最后一行输出.为什么添加三个1的字节

这称为符号扩展.当较小的有符号数被分配(转换)为较大的数字时,其符号位被复制以确保它代表相同的数字(例如1s和2s补码).


错误的printf格式说明符
您正在尝试打印char指定的"%u"指定符unsigned [int].与转换说明符不匹配的参数printf7.19.6.1第9段中的未定义行为.

如果转换规范无效,则行为未定义.如果任何参数不是相应转换规范的正确类型,则行为未定义.

char用于存储带符号值的用途
另外为了确保char包含signed值,显式使用signed charas char可能表现为signed charunsigned char.(在后一种情况下,您的代码段的输出可能是205 205).在gcc你可以强制char表现为unsigned char-funsigned-char选项.

  • 如果它实际上是未定义的行为,这是有争议的.因为提升后的参数是`int`类型,而不是`char`.最好的是完全避免printf系列功能,因为它们很容易受到UB的影响.出于相同类型的安全原因,还应避免使用变量函数. (2认同)