使用 scanf 从控制台读取无限行

reading an unbounded line from the console with scanf

我需要读取一个有限但长度没有限制的字符串。 我们只了解了 scanf,所以我想我不能使用 fgets。 无论如何,我 运行 这个代码在一个长度大于 5 的输入上。

char arr[5];
scanf("%s", arr);

char *s = arr;
while (*s != '[=10=]')
    printf("%c", *s++);

scanf 一直在扫描和写入溢出的部分,但它看起来像 hack。这是一个好习惯吗?如果没有,应该怎么读?

注意:我们已经了解了 alloc 函数系列。

scanf 是这项工作的错误工具(对于大多数工作)。如果您需要使用此功能,请使用 scanf("%c", &c).

一次阅读一个 char

您的代码误用了 scanf():您正在传递 arr,指向 char 的指针数组的地址,而不是 char.[=30 的数组=]

你应该分配一个charmalloc的数组,读入字符,当它太小时用realloc扩展它,直到你得到一个[=22] =] 或 EOF.

如果你可以倒回 stdin,你可以先用 scanf("%*s%n", &n); 计算要读取的字符数,然后将目标数组分配给 n+1 字节,rewind(stdin);并使用 scanf("%s", buf); 将字符串重新读入缓冲区。 这是一项有风险的业务,因为某些流(例如控制台输入)无法倒带。

例如:

fpos_t pos;
int n = 0;
char *buf;

fgetpos(stdin, &pos);
scanf("%*[^\n]%n", &n);
fsetpos(stdin, &pos);
buf = calloc(n+1, 1);
scanf("%[^\n]", buf);

因为你应该只知道一些基本的 C,我怀疑这个解决方案是否符合你的预期,但我想不出任何其他方法来使用标准 C 一步读取无界字符串.

如果您使用的是 glibc 并且可能会使用扩展,您可以这样做:

scanf("%a[^\n]", &buf);

PS: 故意忽略所有错误检查和处理,但应在您实际分配时处理。

尝试限制接受的字符数:

scanf("%4s", arr);

只是你写的超过了arr[5]。 "Hopefully" 您一直在进程的分配内存上写入,但如果超出范围,您将得到 segmentation fault

%as%ms(POSIX) 可用于此目的如果您将 gcc 与 glibc 一起使用。(不是 C 标准)

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

int main(void){
    char *s;
    scanf("%as", &s);
    printf("%s\n", s);
    free(s);
    return 0;
}

缓冲区溢出是一种瘟疫,是最著名但最难以捉摸的错误之一。所以你绝对不应该依赖他们。

既然您了解了 malloc() 和朋友,我想您应该会利用它们。

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

// Array growing step size
#define CHUNK_SIZE  8

int main(void) {
    size_t arrSize = CHUNK_SIZE;
    char *arr = malloc(arrSize);
    if(!arr) {
            fprintf(stderr, "Initial allocation failed.\n");
            goto failure;
        }

    // One past the end of the array
    // (next insertion position)
    size_t arrEnd = 0u;

    for(char c = '[=10=]'; c != '\n';) {
        if(scanf("%c", &c) != 1) {
            fprintf(stderr, "Reading character %zu failed.\n", arrEnd);
            goto failure;
        }

        // No more room, grow the array
        // (-1) takes into account the
        // nul terminator.
        if(arrEnd == arrSize - 1) {
            arrSize += CHUNK_SIZE;
            char *newArr = realloc(arr, arrSize);
            if(!newArr) {
                fprintf(stderr, "Reallocation failed.\n");
                goto failure;
            }
            arr = newArr;

            // Debug output
            arr[arrEnd] = '[=10=]';
            printf("> %s\n", arr);
            // Debug output
        }

        // Append the character and
        // advance the end index
        arr[arrEnd++] = c;
    }
    // Nul-terminate the array
    arr[arrEnd++] = '[=10=]';

    // Done !
    printf("%s", arr);

    free(arr);
    return 0;

failure:
    free(arr);
    return 1;
}

考虑

1) malloc() 在许多系统上只分配内存,不使用它。直到分配内存后,才会出现下划线的物理内存使用情况。参见 Why is malloc not "using up" the memory on my computer?

2) 无限的用户输入是不现实的。鉴于应该使用一些上限来防止黑客和恶意用户,简单地使用大缓冲区。

如果您的系统可以使用这两个想法:

char *buf = malloc(1000000);
if (buf == NULL) return NULL; // Out_of_memory
if (scanf("%999999s", buf) != 1) { free(buf); return NULL; } //EOF

// Now right-size buffer
size_t size = strlen(buf) + 1;
char *tmp = realloc(buf, size);
if (tmp == NULL) { free(buf);  return NULL; } // Out_of_memory
return tmp;

根据 @chqrlie 条评论进行了修正。