在 C 中使用 malloc 确保用户输入某些数字

Using malloc in C to make sure that user enter certain digits

所以我想写一个程序来检查用户是否输入。假设要求是 4 位数字,如果用户输入 5,则程序会不断要求用户租用恰好 4 位数字。

我得到了这样的工作代码:基本上使用 scanf 读取字符串值,然后使用 strlen 计算位数。如果用户输入了正确的数字,那么我会使用 atoi 将该字符串转换为 int,我稍后会用到它。 假设要求是 4 位数字:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int digit, mynumber;        
    int digit = 5;
    char str[5]; 

    /* Checking if enter the correct digit */
    do {
        printf("Enter a %d digit number\n", digit);
        scanf("%s", &str);
        if (strlen(str) != digit) {
            printf("You entered %d digits. Try again \n", strlen(str));
        } else { 
            printf("You entered %d digits. \n", strlen(str)); 
            printf("Converting string to num.....\n"); 
            mynumber = atoi(str); 
            printf("The number is %d\n", mynumber); 
        } 
    } while (strlen(str) != digit);
    return 0;
}

我想稍微修改一下。而不是对 5 位数字字符串执行 char str[5]。我想尝试动态数组。

所以代替 char str[5],我这样做:

char *str;
str = malloc(sizeof(char) * digit);

运行 这通过代码给出段错误。谁能帮我这个?

这是有问题的完整代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    int mynumber;
    int digit = 5;
    char *str;
    str = malloc(sizeof(char) * digit);

    /* Checking if enter the correct digit */
    do {
        printf("Enter a %d digit number\n", digit);
        scanf("%s", &str);
        if (strlen(str) != digit) {
            printf("You entered %d digits. Try again \n", strlen(str));
        } else {
            printf("You entered %d digits. \n", strlen(str));
            printf("Converting string to num.....\n");
            mynumber = atoi(str);
            printf("The number is %d\n", mynumber);
        }
    } while (strlen(str) != digit);
    return 0;
}

在您的代码中,您有一个 5 字节的数组。如果用户输入的数字超过 4 位,scanf 将愉快地溢出数组并破坏内存。在这两种情况下,它都会破坏内存,作为数组和 malloc。但是,并非每次内存损坏都会导致崩溃。

因此,您需要限制 scanf 可以读取的字节数。方法是在格式字符串中使用 %4s

但是,在这种情况下,当用户输入的数字超过 4 位时,您将无法检测到。您至少还需要 1 个字节:str[6]%5s

我建议不要使用 scanf,而是使用 getchar。它将允许您逐个字符地阅读并在途中计算它们。

虽然您可以使用格式化输入函数 scanf 将您的输入作为字符串,但 scanf 充满了一些陷阱,可能会在您的输入流中留下杂散字符(stdin) 取决于是否发生 匹配失败 。它还具有使用 "%s" 转换说明符 的限制,即只能读取第一个 空白 。如果您的用户输入 "123 45",您阅读 "123",您的测试失败,并且 "45" 留在 stdin 未读,等待您下次尝试阅读除非你手动清空 stdin.

此外,如果您使用的 "%s" 没有 field-width 修饰符——您还不如使用 gets() 作为 scanf 将愉快地将无限数量的字符读入您的 5 或 6 字符数组,写入超出数组范围的调用 Undefined Behavior.

一个更合理的方法是提供一个足够大的字符缓冲区来处理用户可能输入的任何内容。 (不要吝啬缓冲区大小)。使用 fgets() 一次读取整行,它有足够大的缓冲区确保整行都被消耗,消除字符在 stdin 中保持未读的机会。 fgets(以及每个 line-oriented 输入函数,如 POSIX getline)的唯一警告是 '\n' 也被读取和包含在填充的缓冲区中。您只需使用 strcspn() 从末尾 trim '\n' 作为获取同时输入的字符数的便捷方法。

(注意: 如果您调整测试以在长度中包含 '\n',则您可以放弃 trimming '\n'验证反对,因为转换为 int 将忽略尾随 '\n')

您的逻辑缺少其他必要的检查。如果用户输入 "123a5" 怎么办? 5 个字符都已输入,但并非全是数字。 atoi() 没有错误报告功能,并且会很乐意将字符串默默地转换为 123,而不提供任何剩余字符的指示。您有两个选择,要么使用 strtol 进行转换并验证没有字符剩余,要么简单地遍历字符串中的字符,用 isdigit() 检查每个字符以确保输入了所有数字。

总而言之,您可以执行以下操作:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define NDIGITS     5   /* if you need a constant, #define one (or more) */
#define MAXC     1024

int main (void) {

    int mynumber;
    size_t digit = NDIGITS;        
    char buf[MAXC];                         /* buffer to hold MAXC chars */

    /* infinite loop until valid string entered, or manual EOF generated */
    for (;;) {
        size_t len;
        printf("\nEnter a %zu digit number: ", digit);  /* prompt */
        if (!fgets (buf, sizeof buf, stdin)) {          /* read entire line */
            fputs ("(user canceled input)\n", stdout);
            break;
        }
        buf[(len = strcspn(buf, "\n"))] = 0;            /* trim \n, get len */
        if (len != digit) {                             /* validate length */
            fprintf(stderr, "  error: %zu characters.\n", len);
            continue;
        }
        for (size_t i = 0; i < len; i++) {              /* validate all digits */
            if (!isdigit(buf[i])) {
                fprintf (stderr, "  error: buf[%zu] is non-digit '%c'.\n",
                        i, buf[i]);
                goto getnext;
            }
        }
        if (sscanf (buf, "%d", &mynumber) == 1) {   /* validate converstion */
            printf ("you entered %zu digits, mynumber = %d\n", len, mynumber);
            break;      /* all criteria met, break loop */
        }
        getnext:; 
    }
    return 0;
}

示例Use/Output

每当您编写输入例程时,请尝试打破它。验证它做了你需要它做的事情并捕获了你想要防止的情况(并且你仍然可以添加更多验证)。在这里,它涵盖了大多数预期的滥用行为:

$ ./bin/only5digits

Enter a 5 digit number: no
  error: 2 characters.

Enter a 5 digit number: 123a5
  error: buf[3] is non-digit 'a'.

Enter a 5 digit number: 123 45
  error: 6 characters.

Enter a 5 digit number: ;alsdhif aij;ioj34 ;alfj a!%#$%$ ("cat steps on keyboard...")
  error: 61 characters.

Enter a 5 digit number: 1234
  error: 4 characters.

Enter a 5 digit number: 123456
  error: 6 characters.

Enter a 5 digit number: 12345
you entered 5 digits, mynumber = 12345

用户在 Linux 上使用 ctrl+d 取消输入(或在 windows 上使用 ctrl+z)生成手册 EOF:

$ ./bin/only5digits

Enter a 5 digit number: (user canceled input)

(注意:您可以添加额外的检查以查看是否输入了 1024 个或更多字符——这是留给您的)

这是读取输入的一种略有不同的方法,但从一般规则的角度来看,在获取用户输入时,如果您确保使用整行输入,就可以避免许多与使用 [=13 相关的陷阱=] 为此目的。

检查一下,如果您还有其他问题,请告诉我。