【问题标题】:how to avoid wrapping of input data in c using gcc?如何避免使用 gcc 在 c 中包装输入数据?
【发布时间】:2016-07-01 06:43:38
【问题描述】:
#include<stdio.h>
void main()
{
    unsigned int a;
    printf("Enter a number:");
    scanf("%u",&a);
    if ( a <= 4294967295) {
        printf("Entered no is within limit\n");
    } 
    else {
        printf("Entered no is not in the limit");
    }
}

哪个输入数字会在上述条件下执行else块?

if 块总是被执行,尽管输入大于限制。这是因为包装。有什么方法可以查出包裹吗?

这是因为 unsigned int 的最大限制是 4294967295,它是二进制 1 的 32 位 如果我的输入是 4294967296,它将变为 1,后跟 32 位 0。 我们无法访问 1 的第 33 位 是否有可能访问第 33 位?

【问题讨论】:

  • 鉴于您有 32 位整数,条件将始终为真。就是这样。
  • 如前所述,您很可能需要 64 位整数,它们的可用性可能取决于您使用的架构。您使用的是哪种架构?或者至少告诉我们sizeof(long)sizeof(long long) gcc 报告了什么。如果是4,那么你就不走运了(或者至少问题变得更加困难),如果其中一个大于4,那么你可以使用它而不是unsigned
  • 使用 long long 变量而不是 unsigned int。 scanf 的格式为“%ld”或“%”PRId64
  • @pradeep - 是的。如前所述,使用超过 32 位的变量。
  • 您实际上应该从编译器收到“条件始终为真”警告。

标签: c linux gcc embedded-linux integer-overflow


【解决方案1】:

哪个输入数字会在上述条件下执行else块?

您的系统很可能使用最大值为 4294967295 的 32 位无符号整数。

如果是这样,则没有可以触发else 块的输入。

正如许多人评论的那样,简单的解决方案是使用具有更多位的变量(如果可能)。但正如@Brendan 指出的那样,它只会给您带来其他问题。

更健壮的解决方案是编写自己的函数来解析输入,而不是使用scanf

这样的功能可能是这样的:

#include <stdio.h>
#include <limits.h>

// Returns 1 if the string can by converted to unsigned int without overflow
// else return 0
int string2unsigned(const char* s, unsigned int* u)
{
    unsigned int t = 0;
    if (*s > '9' || *s < '0') return 0;      // Check for unexpected char
    while(*s)
    {
        if (*s == '\n') break;                   // Stop if '\n' is found 
        if (*s > '9' || *s < '0') return 0;      // Check for unexpected char
        if (t > UINT_MAX/10) return 0;           // Check for overflow
        t = 10 * t;
        if (t > UINT_MAX - (*s - '0')) return 0; // Check for overflow
        t = t + (*s - '0');
        s++;
    }
    *u = t;
    return 1;
}

int main(void) {
    unsigned int u;
    char s[100];
    if (!fgets(s, 100, stdin))   // Use fgets to read a line
    {
        printf("Failed to read input\n");
    }
    else
    {
        if (string2unsigned(s, &u))
        {
            printf("OK %u\n", u);
        }
        else
        {
            printf("Illegal %s", s);
        }
    }
    return 0;
}

例子:

input: 4294967295
output: OK 4294967295

 input: 4294967296
 output: Illegal 4294967296

(感谢@chux 为我的原始代码提出了更好的替代方案)

【讨论】:

  • string2unsigned() 以典型输入失败。
  • if (v/10 != t)if (t &gt; UINT_MAX/10) return 0; t *= 10;的替代品
  • @chux - 你的替代方案比我的原始代码好得多,所以我更新了代码 - 谢谢。我还添加了一些额外的检查并将函数更改为在\n 停止,这似乎更合乎逻辑,因为我直接使用来自fgets 的输入调用它。感谢您指出问题。
【解决方案2】:

使用更大的整数类型(具有更多位)是错误的和损坏的。这样做只会给您带来一个新问题(例如,在不应该接受高于 18446744073709551615 的数字时接受)。

用于用户界面设计;单个“输入的数字不在限制范围内”错误消息是不可接受的。您必须告诉用户真正的问题是什么,并提醒用户您的期望。

至少,您应该处理至少 4 种不同类型的错误(因此您应该能够显示至少 4 种不同的错误消息):

  • 没有收到任何输入(stdin 给了你一个EOF

  • 输入包含无效字符(例如字节 0x00、格式错误的 UTF-8 多字节序列、高于 0x7F 的 ASCII 字符等)

  • 输入包含无法识别/未接受的字符(例如,用户输入了“Bork!”、“0x1234”或“12”;或者用户输入了“12,345.00”并且您的代码无法处理千位分隔符或分数)

  • 输入是有效数字,但不在特定范围内。这可能包括负值,例如“-1234”(在这种情况下,减号不应被视为无法识别/不接受的字符,从而导致给出错误的错误消息)。

还要注意,限制不取决于你使用的变量类型的大小;您选择的变量类型的大小取决于限制。例如,如果您要求某人输入他们的年龄并且他们输入“12345”,那么这应该会导致一条错误消息,例如“数字超出范围(年龄必须在 1 到 150 之间)”。不管它是否导致溢出。在这种情况下,限制将是 150(而不是像 INT_MAX 这样的东西,因为没有人可能会那么老)。由于限制为 150,因此您不能选择使用 signed char 作为变量类型,但可以选择使用 uint8_t(或任何更大的值)。

考虑到所有这些; scanf() 永远无法用于解析“来自人类的字符串”。您必须编写自己的解析器或找到合适的东西(“非标准”库)。

【讨论】:

  • 使用更多位的好处 - 这不是解决方案。不错的收获。
猜你喜欢
  • 1970-01-01
  • 2019-10-07
  • 2015-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-24
  • 2016-02-19
  • 1970-01-01
相关资源
最近更新 更多