c语言 - 可以 '无符号的 int x;scanf( "%u" ,&x);' 真的会导致未定义的行为吗?



有一次我认为我发现了sscan()的良好用途,但在阅读了它如何处理整数后,它似乎没有。有一个字符串应该是这样的:123,456,678我想我可以用以下代码安全简洁地解析它:

unsigned int x[3];
if( sscanf( s, "%u,%u,%u", x+0, x+1, x+2 ) == 3 )
…

如果转换失败,我真的不想知道为什么,也不担心得到不正确的数据。如果里面有数字以外的东西,scanf()肯定会创建一个匹配错误并中止,而且它知道我在寻找一个无符号整数,所以任何负数也应该是匹配错误?没有。

当我读到转换说明符%u时,我开始怀疑:匹配一个可选的带符号的十进制整数为什么这不是一个匹配错误?如果签名了会发生什么?

引用ISO/IEC 9899:201x 7.21.6.2¶10,fscanf函数(强调矿):

除了%说明符之外,输入项(或者,在%n指令的情况下输入字符的计数)被转换为适合于转换说明符的类型。如果输入项不是匹配的序列,指令的执行失败:这条件是匹配失败。除非分配抑制用a*表示,否则转换的结果被放置在下面第一个参数所指向的对象中尚未接收到转换结果的格式参数。如果此对象没有合适的类型,或者如果无法表示转换结果,则为在对象中,行为是未定义的

它看起来就像scanf()对待每个看起来像整数的转换说明符一样,将输入读取为某种未指定大小的有符号整数,然后绕过所有正常转换写入输出

例如,根据正常的隐式转换,将任何整数(负或正)转换为较小大小的无符号整数都表现良好,但scanf():则不然

unsigned int x;
x = -1;                   /* Well defined: (-1) + (UINT_MAX+1) = UINT_MAX */
sscanf( "-1", "%u", &x ); /* Undefined behavior? */

请告诉我我错了,我错过了标准的某些部分。有一件事我真的找不到参考,那就是上面引用的部分:"输入项(…)被转换成适合于转换说明符"的类型。如果转换说明符是%u,那么任何负数当然都不适合,任何不适合无符号整数的东西也不适合。然而,我在标准中找不到任何东西确切地告诉我什么是";适当类型";是.

我发现了一些直接或间接涉及这方面的问题,但没有太多细节。与我的问题最相似的是C:如何防止使用scanf的输入溢出?但它的框架并不那么具体。一些答案(1,2)提到了这个问题,但没有提供细节或参考。

我这个问题的目的是得到一个答案,详细说明为什么除了未定义的行为之外,不能以任何方式解释这一点,最好是关于为什么这是有意义的一些理由-完全知道C中的一些东西是不一致的,你我必须接受它。

有一次我认为我发现了sscan()的良好用途,但在阅读了它如何处理整数后,它似乎不是

至于建议忽略C中的工具,因为它可能很危险,我经常把它看作是对抗scanfgoto甚至传统C字符串的武器。。。但最终,整个语言都会带来微妙的危险,所以你最好遵循的建议(正确地)为工作使用正确的工具,而C大多是而不是大多数工作的正确工具!记住这一点;有时,这种对货物的狂热思维会让你看不到最好的工具。此外,关于的正确性,我相信您已经意识到,您应该考虑大多数标准库函数的返回值,正是由于这些常见的遗漏,才产生了这种货物狂热的想法。要正确使用工具,我们必须阅读其手册,fscanf手册非常清楚地表明了返回值的重要性。看到人们阅读这样的手册(感谢您提出这样的问题)

就你的问题而言,我已经详细列出了我认为你想要答案的问题,并将回到这些问题上来。然而,首先,你似乎得出了一些不准确的前提。例如,你可能在第7.21.6.2节第9段(实际上是你引用的第10页之前的一段)中掩盖了一些必要的细节,所以很难说你对";输入项";是正确的:

输入项被定义为输入字符的最长序列,该序列不超过任何指定的字段宽度,并且是匹配输入序列的前缀。

所以事实上你后来的问题是:

如果签名,会发生什么

。。。本质上与相同

当(字符序列)输入项以"-"字符开头时会发生什么?

我不能确定会发生什么,因为您的实现有很多选项可供选择,而且似乎取决于标准库。在标准中有几个地方无法进行优化;就好像";第5.1.2.3p4和p6节中的规则。将实施细节留给实施的原因是为了让实施机会得以优化,否则这是不可能的。可以说,将发生转换。在这个答案中,我将给出一种标准库可以满足这一要求的方法(一个转换),但请放心,这只是一种可能性,还有很多其他可能性,您的编译器甚至可能会将此代码替换为更优化的代码(

一个不同的转换其他章节描述了有符号到无符号转换的详细信息,如第6.3.1.3p2节,,无未定义行为

否则,如果新类型是无符号的,则通过重复地将新类型中可以表示的最大值多加或减去一来转换该值,直到该值在新类型的范围内为止。

当输入以负号开始时,与scanf相关的函数会沿着该逻辑线执行显式转换,或者它们会使用其中一个运算符(如6.3中所述)来提供转换。例如,在标准库中,它可能看起来像:

int c = fgetc(file);
unsigned u = 0;
switch (c) {
case '-':
{ int d = 0;
while (isnum(c = fgetc(file)))
{ d *= 10;
d -= (c - '0');
}
if (c >= 0) ungetc(c, file);
u = d; // here's your signed-to-unsigned conversion, with no UB
break;
}
default:
while (isnum(c))
{ u *= 10;
u += c;
c = fgetc(file);
}
if (c >= 0) ungetc(c, file);
}

现在看来,既然我们已经展示了标准库在这种情况下是如何遵守的,那么就转到另一个问题(你的第一个问题):

为什么这不是一个匹配错误

用敏锐的眼光,你可能会注意到我的代码可以不那么口是心非。如果非要我大胆猜测的话,他们希望对代码进行模块化,以减少一级缓存的抖动(因为这曾经是一个比现在更大的问题),他们设计了巧妙的模式,用相同的逻辑匹配各种数字数据。对于pp数字元素,你可以问同样的问题,答案是一样的:如果C在实践中没有";就好像";规则

相关内容

最新更新