【问题标题】:Reading signed char using %u使用 %u 读取有符号字符
【发布时间】:2016-07-20 21:51:13
【问题描述】:
#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));

}

这个程序在我的 32 位 Ubuntu 机器上生成以下输出。

21  205  91  7  
-51
4294967245

前两行输出我能理解 =>

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

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

【问题讨论】:

  • 因为%u 用于无符号整数,而您的*(ccc+1) 是负字符。无符号整数大小比 Char 大小长,所以有填充。由于您的 char 是负数,所以有 1 填充,因为正字符会有 0 填充。

标签: c++ c pointers char pointer-arithmetic


【解决方案1】:

-51 以 8 位十六进制存储为 0xCD。 (假设2s补码二进制)

当您将它传递给 variadic function 时,例如 printf,默认参数提升发生,char 提升为 int,表示为 0xFFFFFFCD(对于 4 字节 int)。

0xFFFFFFCD 解释为int-51 并解释为unsigned int4294967245

延伸阅读:Default argument promotions in C function calls


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

这称为sign extension。当较小的有符号数被分配(转换)为较大的数时,它的有符号位会被复制以确保它代表相同的数字(例如在 1 和2s compliment 中)。


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

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

使用char 存储签名值
还要确保char 包含signed 值,明确使用signed char,因为char 可能表现为signed charunsigned char。 (在后一种情况下,您的 sn-p 的输出可能是 205 205)。在gcc 中,您可以使用-funsigned-char 选项强制char 表现为unsigned char

【讨论】:

  • 如果它实际上是未定义的行为是有争议的。因为提升后的参数类型是int,而不是char。最好的当然是完全避免 printf 系列函数,因为它们很容易出现 UB。出于同样的安全原因,也应避免使用可变函数。
  • @Lundin 谢谢。这条评论实际上完成了答案。
  • 编译没有产生任何关于 %u 的警告。谢谢。
【解决方案2】:

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

123456789d = 075BCD15h
Little endian: 15 CD 5B 07

因此 v+1 给出值0xCD。当它存储在有符号字符中时,您会得到有符号十进制格式的-51

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

最后,%u 说明符告诉 printf 将其视为无符号整数,因此您最终得到 42.9 亿。

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

【讨论】:

  • "这里要理解的重要部分是 %u 与实际的类型提升无关,它只是告诉 printf 如何解释提升后的数据。" - 很好的解释:) 谢谢
  • 根据 7.21.6.1,传递无效类型是未定义的。 char 需要 hhd 和 unsigned char hhu。据我理解的文本,参数是促销前的类型,因为 7.21.6.1,p7 中的文本:参数将根据整数促销进行提升我不' t 认为使用 d 打印 unsigned char 并依赖整数提升是有效的。你怎么看?
  • @2501 我认为 C 标准不清楚。我不明白为什么 fprintf 应该规定一些未定义行为的特殊情况,这会以某种方式覆盖变量参数列表参数的参数提升的明确定义的行为。显然,如果我做了一些完全奇怪的事情,比如在传递浮点数时使用%d,就会出现未定义的行为。为什么在混合不同的、否则兼容的整数类型时会有未定义的行为不太明显。
  • @Lundin Well 7.21.6.1,p9 指示 ub。一个人可能不喜欢它,但文字在那里。我同意目前还不清楚。如果在促销之后我们仍然有争论,那么它不是 ub。
  • @2501 我想问题的一部分是较小整数类型的说明符仍然必须产生明确定义的行为。例如,uint8_t x; printf("%" PRIu8, x) 是明确定义的,尽管从技术上讲,x 总是会被提升为 int。我一直认为它是“如果您使用正确的格式说明符,那么获得正确的结果不再是程序员的责任”。然后以隐式提升的形式在两行之间发生的事情是编译器要处理的问题。
猜你喜欢
  • 2012-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多