我已经尝试了scanf("%u",&number)
并输入了负数,问题是当我printf("%d",number)
得到负数时。我以为这会阻止我阅读负数。
scanf("%d",&number)
和scanf("%u",&number)
真的一样吗?
或者只是为了可读性。
我在做一些叫做未定义行为的事情吗?
编辑:
从Wikipedia我读到了这个:
%u:扫描十进制无符号整数(注意在C99标准中 输入值减号是可选的,因此如果读取减号,则不 将出现错误,结果将是a的两个补码 负数,可能是一个非常大的值。
阅读SO答案及以上内容有点令人困惑。有人能说得更清楚吗?
答案 0 :(得分:2)
是的,无论如何,这都是未定义的行为。
考虑变量number
的类型为unsigned
,%d
中的printf()
期望参数为signed
int类型,并传递unsigned
类型是UB。
OTOH,如果number
是签名类型,则使用%u
进行扫描首先是UB。
正如您所期望的那样
[...]阻止我阅读负数
格式说明符不,阻止输入不正确。如果格式说明符与提供的参数不匹配,则会调用undefined behavior。
引用C11
,附件J.2,调用UB的场景,
其中一个格式化输入函数的转换结果不能 在相应的对象中表示,或者接收对象没有 适当的类型
答案 1 :(得分:1)
正如Sourav Ghosh详细解释的那样,使用与实际类型不一致的格式是一个潜在的问题。然而对于这种特殊情况,在当前的PC体系结构中,这不是一个真正的问题,因为int
和unsigned int
都没有陷阱表示。
您可以使用scanf("%u", &number);
扫描负数。它将在目标类型中被否定,即unsigned int
,具有与带符号int
中的负数相同的按位表示,用于二进制补码表示,这在当前体系结构上几乎是通用的。
scanf
通过匹配可选的带符号十进制整数来转换%u
,其格式与strtoul
函数的主题序列的预期相同,值为10
基本论点。相应的参数应该是指向无符号整数的指针。
strtol
,strtoll
,strtoul
和strtoull
函数将nptr
指向的字符串的初始部分转换为long int
,long long int
,unsigned long int
和unsigned long long int
表示。首先,它们将输入字符串分解为三个部分:初始的,可能为空的白色空格字符序列(由isspace
函数指定),类似于由值确定的某个基数表示的整数的主题序列base,以及一个或多个无法识别的字符的最终字符串,包括输入字符串的终止空字符。然后,他们尝试将主题序列转换为整数,并返回结果。如果base的值在2到36之间(包括),则主题序列的预期形式是一个字母和数字序列,表示一个整数,其基数由base指定,可选地前后加号或减号,但不包括整数后缀。
...如果主题序列具有预期形式且base的值在2到36之间,则将其用作转换的基础,将其值归于每个字母,如上所述。如果主题序列以减号开头,则转换产生的值将被否定(在返回类型中)。
如果number
的类型为unsigned int
,则定义行为并使用无符号否定语义解析负值并将其存储到number
中。使用printf("%d", number);
打印此值最多只是实现定义,但在当前的PC架构上,将打印最初由scanf("%u", &number);
解析的负数
结论:虽然它似乎无害,但在int
和unsigned int
中使用printf
和scanf
并使用错误的格式非常草率。事实上,在表达式中混合有符号和无符号类型,尤其是在比较中非常容易出错,因为这种结构的C语义有时会违反直觉。