在C语言中将输入作为字符串处理

4
这是一个相当重要的问题,请花些时间仔细阅读并提供答案。 我的问题是,在C中如何以字符串形式输入?通常我们要求用户提供字符数,比如说n,并且我们可以简单地声明char str[n]。这会很好。 但是,当我们通常像char str[100]这样声明大小时,如果我们提供一个长度为20的字符串,则会浪费80个字节,我们通常不希望出现这种情况,这样声明是否可以? 如果用户输入了120个字符,那么只有100个字符将存储在我们的字符数组中,我们也不希望出现这种情况。 所以,基本上我们不知道用户可能输入什么。他输入一个长度,由他自己选择。 在上述情况下,我们使用scanf或gets进行输入,例如scanf("%s",str)、scanf("%[^\n]%*c",str)、scanf("%[^\n]s",str)、gets(str)等。 当我们使用scanf时,当我们输入一个长度为5的字符串并给出6个字符时,第6个字符将无法存储。 当我们使用puts时,当我们输入一个长度为5的字符串并给出6个字符时,第6个字符将存储在连续的字节中,但是当我们尝试打印时,第6个字符将无法显示。当我们输入6个字符时,它会显示“stack smashing detected”的消息。我们不知道其他数据是否存在,可能会被覆盖。 上述情况是正确还是错误的,请帮助我吗? 现在,有另一种方式声明字符串并将其作为字符串输入,我们可以使用指针,比如我们可以动态分配内存,然后在完成字符串工作时释放内存。我们使用malloc、calloc、realloc来分配内存,并使用free来释放内存。 我们可以声明char* str=(char*)malloc(size*sizeof(char)),并且我们以scanf("%[^\n]s",str)的形式输入。但是,这里我们也需要提供大小。如果我们不知道大小怎么办?如果用户提供的输入大于大小怎么办? 我们还可以声明char* str=(char*)malloc(sizeof(char))。在这里,当我们输入一个长度为5的字符串时。字符串以连续的字节存储在堆中,但我们只分配了1个字节,我们输入的剩余4个字节以一种非法的内存访问方式存储,我们可以这样做吗?

上述提到的两种情况是相同的,这是正确的还是错误的?您能帮我吗?

我处于“奇步逼和”的情况中(象棋术语)。您能帮我吗?有哪些方式可以声明字符串并输入而不指定大小?我们能否在不指定大小的情况下动态分配内存?有哪些声明字符串的方式?


1
你可以为字符串输入创建一个缓冲区,它具有最大的使用情况大小,例如 char buf[4096]。然后,接受输入到 buf 中,随后使用 strlen(buf) 获取该值并动态分配实际的字符串空间。 - PHD
嗨PHD,我会做那个,但缓冲区本身会占用栈上的空间,在函数结束后,该空间会被清除,是这样吗?有没有办法明确地删除内存buf[4096]? - Goutham18
@Goutham18 在堆栈上删除内存是不可能的。作用域取决于您如何定义它(自动或静态)。 - PHD
如果由于某种原因,堆栈中的内存是一个问题(反正一旦退出读取函数,该帧就会丢失),您可以在堆上手动分配缓冲区。 - Miguel Sandoval
与此类似的答案中有相关的想法。在Linux上,您有4M的堆栈,在Windows上有1M(在大多数情况下),因此浪费80个字符左右的底线是,这在Windows上占用了0.0076%(7.6e-5分数)的堆栈空间,在Linux上占用了0.0019%的堆栈空间。我宁愿每天都多10,000个字符,也不要少一个.... - David C. Rankin
显示剩余10条评论
3个回答

1
从手册上看,getline(3) 是你要找的东西。
   #include <stdio.h>

   ssize_t getline(char **restrict lineptr, size_t *restrict n,
                   FILE *restrict stream);

这是其中的一小段文字:
getline() 函数从流中读取整行内容,将包含文本的缓冲区地址存储到 *lineptr 中。该缓冲区以空字符结尾,并包括换行符(如果找到)。
如果在调用之前将 *lineptr 设置为 NULL 并将 *n 设置为 0,则 getline() 将分配一个用于存储行的缓冲区。即使 getline() 失败,用户程序也应释放此缓冲区。
或者,在调用 getline() 之前,*lineptr 可以包含一个指向 malloc(3) 分配的大小为 *n 字节的缓冲区的指针。如果缓冲区不足以容纳行,则 getline() 使用 realloc(3) 调整其大小,并根据需要更新 *lineptr 和 *n。
无论哪种情况,在成功调用后,*lineptr 和 *n 都将更新以反映缓冲区地址和分配大小。
因此,getline 将会使用你提供的缓冲区进行 malloc 或甚至 realloc 操作。有了这个想法,你可以编写像这样的程序:
/* getline.c
 *
 */
#include <stdio.h>

int main(void)
{
    char *s = NULL;
    ssize_t n = 0;

    fprintf(stderr, "Line: ");
    getline(&s, &n, stdin);

    printf("Size: %zu\n", n);
    //printf("String: %s", s);
    
    /* @isrnick comment */
    free(s);

    return 0;
}

然后用类似这样的东西来测试它:

$ make getline
$ python -c "print('A' * 2000000)" | ./getline
Size: 2097664
$

这将打印出已分配缓冲区的大小。由于我们键入 ENTER 来输入一些字符串,而 ENTER 给我们 \n,所以使用 getline 应该没问题。


/* gcat.c
 */

#include <stdio.h>

int main(int argc, char **argv)
{
    char *s;
    ssize_t n;
    FILE *fp = stdin;

    if (argc > 1) {
        if(!(fp = fopen(argv[1], "r"))) {
            perror("fopen");
            return -1;
        }
    }

    while(getline(&s, &n, fp) > 0) 
        printf("%s", s);


    /* @isrnick comment */
    free(s);

    return 0;
}

你可以使用以下任何一种方式来调用它:

$ cat gcat.c | ./gcat

或者...
$ ./gcat gcat·c

1
注意:getline不是标准的C函数,可能默认情况下不可用。 - isrnick
1
动态分配的内存应该被释放。 - isrnick
1
@isrnick 同意,但如果你在 main 函数的 return 语句处,就像上面的例子一样,操作系统(至少是 Linux)会为你处理这个问题。 - Enzo Ferber
1
@isrnick编辑了帖子,在所有操作完成后释放了缓冲区。 - Enzo Ferber
是的,操作系统通常会在进程结束时释放内存,但最好强制自己直接释放程序中的内存,并且不要从来不依赖操作系统来释放它,即使只是养成释放动态分配内存的习惯,以便在实际需要时不忘记这样做。 - isrnick

1

理论

一种解决方案是创建链接的缓冲区结构体。

这样,每次缓冲区用完空间,您只需为另一个缓冲区分配更多内存,并将它们链接在一起。这些缓冲区的链接列表可以不断增长,直到输入完成。

一旦输入完成,您需要为字符串分配一大块连续的内存,然后遍历链接缓冲区的列表,并将数据复制到最终字符串中。

最后,释放链接缓冲区的分配内存。

实际例子

读取任意长度的字符串可以像这样简单:

    int main(int argc, char *argv[])
    {
        char *string = readLine(); //read arbitrary-length string
        printf("%s", string); //print string
        free(string); //dont forget to free the string!
        return 0;
    }

那么让我们自己编写 readLine() 函数。

  1. 创建一个链式缓冲区结构:
    #define LINKEDBUFFER_SIZE 256
    
    struct SLinkedBuffer
    {
        char buffer[LINKEDBUFFER_SIZE];
        int idx;
        struct SLinkedBuffer *next;
    };

    typedef struct SLinkedBuffer LinkedBuffer;
    
    LinkedBuffer *newLinkedBuffer()
    {
        LinkedBuffer *result = (LinkedBuffer *) malloc(sizeof(LinkedBuffer));
        if (result == NULL)
        {
            printf("Error while allocating memory!\n");
            exit(1);
        }
        result->idx = 0;
        result->next = NULL;
        return result;
    }

创建一个读取函数,利用我们刚刚定义的链接缓冲区:
    char *readLine()
    {
        char *result = NULL;
        size_t stringSize = 0;
        
        /* Read into linked buffers */
        LinkedBuffer *baseLinkedBuffer = newLinkedBuffer();
        LinkedBuffer *currentLinkedBuffer = baseLinkedBuffer;
        int currentChar;
        while ((currentChar = fgetc(stdin)) != EOF && currentChar != '\n')
        {
            if (currentLinkedBuffer->idx >= LINKEDBUFFER_SIZE)
            {
                currentLinkedBuffer->next = newLinkedBuffer();
                currentLinkedBuffer = currentLinkedBuffer->next;
            }
            currentLinkedBuffer->buffer[currentLinkedBuffer->idx++] = currentChar;
            stringSize++;
        }
        
        /* Copy to a consecutive string */
        int stringIndex = 0;
        result = malloc(sizeof(char) * (stringSize + 1));
        if (result == NULL)
        {
            printf("Error while allocating memory!\n");
            exit(1);
        }
        currentLinkedBuffer = baseLinkedBuffer;
        while (currentLinkedBuffer != NULL)
        {
            for (int i = 0; i < currentLinkedBuffer->idx; i++)
                result[stringIndex++] = currentLinkedBuffer->buffer[i];
            currentLinkedBuffer = currentLinkedBuffer->next;
        }
        result[stringIndex++] = '\0';
        
        /* Free linked buffers memory */
        while (baseLinkedBuffer != NULL)
        {
            currentLinkedBuffer = baseLinkedBuffer->next;
            free(baseLinkedBuffer);
            baseLinkedBuffer = currentLinkedBuffer;
        }
        
        return result;
    }

现在我们可以使用 readLine() 函数来读取任何字符串,如主函数中所示!

currentChar 应该是 int 而不是 char,以便能够存储 EOF - isrnick

0
这段代码可以帮助你获取一个没有长度限制的字符串。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    read = getline(&line, &len, stdin);
    printf("%s",line);
    printf("%lu",strlen(line));
    free(line);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接