如何仅在数组中有足够的 space 时才保存 scanf 输入?如何重新分配数组以使 scanf 输入适合?

How to save the scanf input only if there's enough space in the array? How to reallocate array to let the scanf input fits in?

#include <stdio.h>

int main() {

    char *mystring = calloc(2, sizeof(char));

    scanf("%10[^\n]s", mystring);

    printf("\nValue: %s\nSize of array: %d\nAllocated space: %d\n",
           mystring, 2 * sizeof(char), sizeof(char) * strlen(mystring));

    free(mystring);
}

输出:

$ ./"dyn_mem" 
laaaaaaaaaaa

Value: laaaaaaaaa
Size of array: 2
Allocated space: 10

如果我在 scanf 输入中输入大于数组大小的字符串,此代码可能会产生未定义的行为。我如何处理这个问题?

选项 #1

来自 Kernighan and Ritchie 2nd ed 附录 B.1.4

char *fgets(char *s, int n, FILE *stream)

fgets 最多读取接下来的 n-1 个字符到数组 s 中,如果换行符则停止 遭遇;换行符包含在以“\0”结尾的数组中。 fgets returns s,如果文件结束或发生错误则为 NULL。

replace n with sizeof(char)*strlen(mystring) in your code

选项#2

也来自 Kernighan and Ritchie 2nd ed 附录 B.1.4

int fgetc(FILE *stream)

fgetc returns 流的下一个字符作为无符号字符(转换为 int) 或 EOF 如果文件结束或发生错误。

并手动放入一个以sizeof(char)*strlen(mystring)为限制的for循环

This code can produce an undefined behavior if I enter in the scanf input a string bigger than array size.

是的。

How can I "handle" this ?

通过确保您始终传递 scanf 指向适合相应转换指令的类型的对象的指针。作为 C 程序员,帽子总是的责任。对于 s[ 指令,"appropriate" 包括足够大以容纳所有可能的转换值。

当格式表示输入的最大大小时,可以很容易地做到这一点,无论是直接的,如示例中的,还是参数化的。格式在您的控制之下。但是,如果您需要处理无限大小的输入,那么 scanf 不能胜任这项任务,至少它本身不能胜任。在这种情况下,您需要实施一种变体来猜测您需要多少 space,如果发现还不够,则获取更多。除其他外,这意味着准备好读取多个输入,并可能通过动态分配为其获得 space。

您的代码中存在多个问题:

  • mystring 被初始化为指向分配的 2 字节块。从技术上讲,您应该测试内存分配失败。

  • 转换格式 "%10[^\n]s" 不正确:应删除结尾的 s,字符 类 的语法以 ] 结尾。

  • 数字10表示最多存储10个字符和一个空终止符到mystring。如果需要存储超过 1 个字符,则代码具有未定义的行为。

  • size_tprintf 转换说明符是 %zu,而不是 %d。如果您的 C 库符合 C99,请使用 %zu,否则将最后 2 个参数设为 (int).

  • 大小输出与标签不对应:第一个是分配的大小,第二个是字符串的长度。

  • 如果文件为空或以换行符开头,scanf() 将失败。您应该测试 scanf() 的 return 值,它必须是 1,以避免在无效输入的情况下出现未定义的行为。

  • sizeof(char) 根据定义是 1

有很多方法可以实现您的目标:

在支持它的系统上,例如带有 GNU lib C 的 linux,您可以在 %[ 之间使用 m 前缀scanf() 转换格式并将 char * 的地址作为参数传递。 scanf() 将分配一个 malloc() 足够大的数组来接收转换后的输入。

这是 linux 的修改版本:

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

int main() {
    char *mystring = NULL;
    if (scanf("%m[^\n]", &mystring) == 1) {
        printf("Value: %s\n"
               "Length of string: %zu\n"
               "Allocated space: %zu\n",
               mystring, strlen(mystring), malloc_usable_size(mystring));
        free(mystring);
    }
    return 0;
}

在 POSIX 系统上,您可以使用 getline() 将一行读入分配的数组。

在其他系统上,您需要编写一个函数来读取输入流并重新分配目标数组,只要您没有换行符或文件结尾即可。

一个常见的折衷方案是对输入的最大长度做出假设:

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

int main() {
    char buf[1024];
    if (scanf("%1023[^\n]", buf) == 1) {
        char *mystring = strdup(buf);
        if (mystring) {
            printf("Value: %s\n"
                   "Length of string: %d\n",
                   "Minimum allocated size: %d\n",
                   mystring, (int)strlen(mystring), (int)strlen(mystring) + 1);
            free(mystring);
        }
    }
    return 0;
}

您还可以使用 fgets() 从输入流中读取一行并去除换行符(如果有)。这种方法的优点是不会在空行上失败。

下面是 getline() 的一个简单实现,应该可以满足您的需要:

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

int my_getline(char **lineptr, size_t *n, FILE *stream) {
    char *ptr = *lineptr;
    size_t size = *n;
    size_t pos = 0;
    int c;
    while ((c = getc(stream) && c != '\n') {
        if (pos + 1 >= size) {
            /* reallocate the array increasing size by the golden ratio */
            size = size + (size / 2) + (size / 8) + 16;
            ptr = realloc(ptr);
            if (ptr == NULL) {
                ungetc(c, stream);
                return EOF;
            }
            *n = size;
            *lineptr = ptr;
        }
        ptr[pos++] = c;
        ptr[pos] = '[=12=]';
    }
    return (int)pos;
}

int main() {
    char *mystring = NULL;  // must be initialized
    size_t size = 0;        // must be initialized
    int res;

    while ((res = my_getline(&mystring, &size, stdin)) >= 0) {
        printf("Value: %s\n"
               "Length of string: %d\n",
               "Allocated size: %d\n",
               mystring, res, (int)size);
    }
    free(mystring);
    return 0;
}