C/C++中unsigned关键字引发的困惑

3
考虑下面这两个C程序。我的问题是在第一个程序中,无符号关键字打印出“-12”,但我认为它应该打印出“4294967284”,但它不会以%d说明符打印它。它会以%u说明符打印它。但是,如果我们看第二个程序,输出是“144”,而不是应该的“-112”。对于我没有理解到的无符号关键字有些可疑。有什么帮助吗,朋友们!
#include <stdio.h>

int main()
{ unsigned int i = -12;
  printf(" i = %d\n",i);
  printf(" i = %u\n",i);
  return 0;
}

我从这个链接中获取了上述程序:如何将负数赋值给无符号整型?
#include <stdio.h>

int main(void)
{unsigned char a=200, b=200, c;
  c = a+b;
 printf("result=%d\n",c);
 return 0;
}
3个回答

12
每个printf格式说明符都需要某种特定类型的参数。 "%d"需要一个int类型的参数;"%u"需要一个unsigned int类型的参数。你完全有责任传递正确类型的参数。
unsigned int i = -12;

-12 是类型为int的。初始化会自动将该值从int转换为unsigned int。转换后的值(为正且非常大)存储在i中。如果intunsigned int都是32位,则存储的值将是4294967284(232-12)。

printf(" i = %d\n",i);

i 的类型为 unsigned int,但是 "%d" 需要一个 int 类型的参数。这种行为在 C 标准中没有定义。通常情况下,存储在 i 中的值将被解释为存储在一个 int 对象中。在大多数系统上,输出将为 i = -12,但您不应该依赖于此。

printf(" i = %u\n",i);

假设之前的语句未造成混乱,这样代码将会正确打印i的值。

对于普通函数而言,如果你调用它们时传入的参数类型不匹配,编译器通常会自动进行隐式转换以适应该参数声明的类型。但是对于像printf这样的可变参数函数,由于编译器无法知道参数期望的类型,因此无法进行隐式类型转换。相反,参数将遵循默认参数提升规则进行处理。如果一个参数类型比int更窄,且int能够容纳该类型的所有值,则该参数将被提升为int,否则将被提升为unsigned int。如果参数类型为float,则将其提升为double(这也就是为什么"%f"可以同时用于floatdouble类型的参数)。

这些规则意味着,一个窄的无符号类型参数通常(但并非总是)会被提升为(有符号)int

unsigned char a=200, b=200, c;

假设每个字节都是8位,ab 被设置为 200

c = a+b;

unsigned char 类型相比,总和 400 太大了。对于无符号算术和转换,超出范围的结果将被减少到类型的范围内。c 被设置为 144

printf("result=%d\n",c);

尽管参数是无符号类型,c的值被提升为int类型;int可以容纳该类型的所有可能值。 输出结果为result=144


感谢您提供正确的答案和详细的解释。 - user3401108

6
在第一个程序中,行为是未定义的。您需要确保格式说明符与参数的数据类型匹配。编译器会生成假设您正确理解的代码;在运行时,它不必进行任何检查(即使它想进行检查,通常也无法进行)。
(例如,库实现printf函数不知道您给出了什么参数,它只看到一些字节,并且必须假设这些字节是您使用%d指定的类型的字节)。
您似乎试图根据具有未定义行为的程序的输出推断出unsigned的含义。那是行不通的。坚持定义良好的程序(最好只阅读unsigned的定义)。
在评论中,您说:
引用:

could give me any reference of unsigned keyword. Still concept is not getting cleared to me. Unsigned definition in C/C++ standard。

在C99标准中,从第6部分开始阅读第6.2.5节。 unsigned int的定义是一种整数类型,可容纳从0到正数UINT_MAX(应该比2的幂少1),必须至少为65535,通常为4294967295。
当您写unsigned int i = -12;时,编译器发现-12超出了unsigned int允许的值范围,因此进行转换。该转换的定义是添加或减去UINT_MAX + 1,直到值在范围内。
您问题的第二部分与所有这些都没有关系。在那个程序中,只有unsigned char而没有unsigned int。
在该程序中,200 + 200得到400。如上所述,由于超出范围,编译器通过减去UCHAR_MAX + 1(即256)将其转换为在范围内。400 - 256 = 144。

1
关于“编译器通过减法进行转换”的说法,在这种情况下是有效的,但仍然具有误导性。通常编译器不涉及强制模运算。在一些已经消亡的架构中(我真的希望Unisys现在已经消亡了),它必须这样做,但它的工作仅限于确保它发生,通常是通过不做任何操作来满足标准要求。 - Cheers and hth. - Alf
@Alf,我认为这是一个过于追求细节的反对意见(尤其是在这个问题所需要的技术水平上)。 - M.M
我希望你能认识到,对于从业者来说,理解事物的工作原理非常重要且节省时间,因此我们向初学者传达这一点也很重要。 - Cheers and hth. - Alf

-4

printf%d%u 格式控制符有将输入整数强制类型转换为 intunsigned int 的功能(或者说负责这个功能)。

实际上,printf (一般来说,任何变参函数)和算术运算符只能接受三种类型的参数(除了格式字符串):4 字节的 int、8 字节的 long longdouble(警告:非常不准确的描述!)。大小小于 int 的任何整数参数都会被扩展为 int。任何浮点参数都会被扩展为 double。这些规则提高了 printf 和算术运算符的输入参数的统一性。

关于你的第二个例子:发生了以下步骤

  1. + 运算符要求 (unsigned) char 操作数扩展为 (unsigned) int 值(在您的情况下是 4 字节整数,我假设)。
  2. 结果总和是一个 4 字节的 unsigned int,值为 400。
  3. 上述总和中只有最低有效的 1 字节可以适配到 unsigned char c 中,因此 c 的值为 400 % 256 == 144
  4. printf 要求所有较小的整数参数扩展为 int,因此 printf 接收到的是一个 4 字节的 int,值为 400。
  5. %d 格式说明符将上述参数打印为 "400"。

搜索“默认参数提升”以获取更多详细信息。


1
printf的%d和%u说明符具有将输入强制转换的能力(或负责此操作)。在我看来,这相当误导人。这些说明符不会触发任何转换,编译器甚至不会查看它们(是的,好的编译器会生成有关错误说明符的警告,但这不是我的重点),也不必知道格式字符串(它可能在编译时未知)。唯一发生的转换是默认参数提升(就像您在第一段之后所说的那样),与格式说明符无关。 - mafso
5
他们的工作是从堆栈(或类似结构)中检索数据,并将其解释为“int”的表示形式。如果调用者没有提供“int”,则行为是未定义的(即使他们提供的表示形式实际上是“int”的有效表示形式)。printf函数无法执行任何转换,因为它不知道所提供参数的数据类型,它只能(在最好的情况下)看到参数在内存中的表示形式。 - M.M
1
它们描述了输入的类型。它们不负责任何事情,是您作为printf的调用者根据它们提供类型的责任。根据标准,第一个示例是UB(类型与格式字符串不对应)。 - mafso
2
"警告:非常不准确的描述!" -- 我同意。-1 - Keith Thompson
3
你为什么假设int是4个字节?在a + b中,操作数被提升为int,而不是unsigned int。提升规则是保留值,而不是保留符号。 printf不会根据格式字符串进行“类型转换”(我猜你的意思是“转换”); 它假定(可能已经提升的)参数已经是正确的类型。 - Keith Thompson
显示剩余11条评论

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