将 getline 与动态存储一起使用

Using getline along with dynamic storage

我有以下片段。我有 across 几个示例,其中可以使用 getline 读取一行,然后简单地打印它。我正在努力不断地从 stdin 保存,使用 getline() 读取并且可能有某种缓冲区来跟踪所有读取的内容(是否需要?)。

最后,我只是按照用户输入最后一行的倒序打印内容。 我不确定 char* 是否可以指向整个缓冲区 os stdin 输入,我们最终可以反向读取。

这是 运行 分段错误,我最好的猜测是访问从未分配的内存。

  size_t linecount = 0;
  ssize_t bytes_read;
  size_t nbytes = 100;
  char *content;

  if (1) {
    my_string = (char*) malloc(nbytes + 1);
    while ((bytes_read = getline(&my_string, &nbytes, stdin)) >= 0
        && my_string[0] != '\n') {
      puts(my_string);
      printf("read: %ld bytes", bytes_read);
      content = (char*) malloc((strlen(my_string) + 1) * sizeof(char));
      success = content != NULL;

      if (success) {
        strcpy(content, my_string);
        ++linecount;
      } else {
        printf("Malloc error\n");
        exit(1);
      }
    }
  }

使用 getline() 读取,如果您提供初始化为 NULL 的指针并且大小参数初始化为零,那么 getline() 将为您的输入分配足够的存储空间——无论长度如何.如果您只是简单地反转字符串,则没有理由分配额外的存储空间。如果要在集合中保存由 getline() 读取的多行(例如使用指针数组,例如 char *lines[NLINES]; 或指向指针的指针,例如 char **lines;)

要使用 getline() 读取所有输入行,打开文件后(或简单地将 stdin 分配给 FILE* 指针),您需要不超过:

    char *lineptr = NULL;       /* pointer for getline() set NULL */
    size_t n = 0;               /* n set to 0, getline() allocates */
    ...
    while (getline (&lineptr, &n, fp) != -1)    /* read every line from file */
        ...

如果只是简单的将每一行的字符反转,可以写一个简单的字符串反转函数,输出反转后的字符串。 getline() 读取循环和一个名为 strrev() 的函数将字符串作为参数:

    while (getline (&lineptr, &n, fp) != -1)        /* read every line from file */
        fputs (strrev (lineptr), stdout);           /* reverse line & output */

制作你的 strrev() 函数来处理字符串,而不管 '\n' 是否存在允许你简单地传递来自 getline() 的包含 '\n' 字符的行最后,不必先 trim 字符串中的换行符。如果您确实想删除 '\n',您可以使用 getline() 返回的字符数,或者只需要简单地调用 strcspn(),例如

    while (getline (&lineptr, &n, fp) != -1) {  /* read every line from file */
        lineptr[strcspn (lineptr, "\n")] = 0;   /* trim '\n' from end of lineptr */
        /* do whatever else is desired */
    }

把它放在一起,并将要读取的文件名作为程序的第一个参数(如果没有给出参数,则默认从 stdin 读取),你可以这样做:

#define _GNU_SOURCE             /* getline() is POSIX not standard C */

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

char *strrev (char *str);       /* function to reverse string in-place         */
                                /* (str must be mutable, not a string-literal) */

int main (int argc, char **argv) {
    
    char *lineptr = NULL;       /* pointer for getline() set NULL */
    size_t n = 0;               /* n set to 0, getline() allocates */
    
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (getline (&lineptr, &n, fp) != -1)        /* read every line from file */
        fputs (strrev (lineptr), stdout);           /* reverse line & output */
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    free (lineptr);    /* getline() allocates, so don't forget to free the memory */
}

/** strrev - reverse string, swaps src & dest each iteration up to '\n'.
 *  Takes valid string and reverses, original is not preserved.
 *  If str is valid, returns pointer to str, NULL otherwise.
 */
char *strrev (char *str)
{
    if (!str) {     /* validate str not NULL */
        printf ("%s() error: invalid string\n", __func__);
        return NULL;
    }
    if (!*str)      /* check empty string */
        return str;
    
    char *begin = str,
         *end = begin + strcspn (str, "\n") - 1;

    while (end > begin)
    {
        char tmp = *end;
        *end--   = *begin;
        *begin++ = tmp;
    }

    return str;
}

例子Use/Output

stdin 读取并在完成后使用 Ctrl + dCtrl + z on windows) :

$ ./bin/getline_strrev
My dog has fleas
saelf sah god yM
My cat has none!
!enon sah tac yM
Lucky cat...
...tac ykcuL

从文件读取(或重定向)输入:

输入文件

$ cat dat/dog.txt
My dog has fleas
My cat has none!
Lucky cat...

例子Use/Output

$ ./bin/getline_strrev dat/dog.txt
saelf sah god yM
!enon sah tac yM
...tac ykcuL

重定向文件 stdin:

$ ./bin/getline_strrev < dat/dog.txt
saelf sah god yM
!enon sah tac yM
...tac ykcuL

反转行而不是字符

根据您在评论中的说明,您可以通过分配指针(并根据需要重新分配)并为每行分配存储空间,分配块的地址来保存每行,从而存储未知长度的未知行数您的每个指针依次复制 getline() 读取的行到分配的块。您有两种类型的内存可以满足,内存块包含指针,内存块包含每一行。 How to read a text file and store in an array in C 一两天前对此进行了非常详细的解释。

该示例与此处的唯一区别是使用 getline() 而不是 fgets() 以及如何计算每行中的字符数并删除结尾 [=缓冲区中的 33=] 略有不同,只是由于 getline() 返回读取的字符数。颠倒行的顺序也不同(但也是微不足道的区别)

getline() 和反转行的变化是:

#define _GNU_SOURCE

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

#define NPTRS 2     /* initial number of pointers to allocated */

int main (int argc, char **argv) {
    
    char **lines = NULL,        /* pointer to all lines */
        *lineptr = NULL;        /* pointer for getline() set NULL */
    size_t  n = 0,              /* n set to 0, getline() allocates */
            nptrs = NPTRS,      /* no. of pointers to allocate */
            used = 0;           /* no. of pointers used */
    ssize_t nchr;               /* no. of chars read by getline() */
    
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    /* allocate initial nptrs pointers - validate EVERY allocation */
    if ((lines = malloc (nptrs * sizeof *lines)) == NULL) {
        perror ("malloc-lines");
        return 1;
    }
    
    /* read every line from file, saving characters read in nchr */
    while ((nchr = getline (&lineptr, &n, fp)) != -1) {
        if (nchr > 0 && lineptr[nchr-1] == '\n')    /* if chars read and \n */
            lineptr[--nchr] = 0;                    /* trim \n from end */
        if (used == nptrs) {
            /* always realloc using a temporary pointer */
            void *tmp = realloc (lines, (2 * nptrs) * sizeof *lines);
            if (!tmp) {                             /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                              /* don't exit, lines still good */
            }
            lines = tmp;                            /* assign reallocated block */
            nptrs *= 2;                             /* update number of pointers */
        }
        /* allocate storage for line, assign address for new block to lines[used] */
        if (!(lines[used] = malloc (nchr + 1))) {
            perror ("malloc-lines[used]");
            break;
        }
        memcpy (lines[used], lineptr, nchr + 1);    /* copy line to new block of mem */
        
        used += 1;      /* increment used pointer counter */
    }
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
        
    /* output lines in reverse order */
    while (used--) {
        puts (lines[used]);
        free (lines[used]);     /* free storage for each string when done */ 
    }
    free (lines);               /* free pointers */
    
    free (lineptr);             /* free memory allocated by getline() */
}

例子Use/Output

与上述示例相同的 inpt 文件。

$ ./bin/getline_linerev dat/dog.txt
Lucky cat...
My cat has none!
My dog has fleas

内存Use/Error检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址 因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入 beyond/outside 您分配的块的边界,尝试读取或基于未初始化的条件跳转值,最后,确认您释放了所有已分配的内存。

对于Linux valgrind是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。

$ valgrind ./bin/getline_linerev dat/dog.txt
==6502== Memcheck, a memory error detector
==6502== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6502== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==6502== Command: ./bin/getline_linerev dat/dog.txt
==6502==
Lucky cat...
My cat has none!
My dog has fleas
==6502==
==6502== HEAP SUMMARY:
==6502==     in use at exit: 0 bytes in 0 blocks
==6502==   total heap usage: 9 allocs, 9 frees, 5,887 bytes allocated
==6502==
==6502== All heap blocks were freed -- no leaks are possible
==6502==
==6502== For counts of detected and suppressed errors, rerun with: -v
==6502== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放所有分配的内存并且没有内存错误。

检查一下,如果您还有其他问题或需要其他帮助,请告诉我。

由于目标是“以用户输入的相反顺序打印内容,最后一行在前”,程序必须存储所有行。 getline() 函数通常为每一行分配相当大的 space(在我的 Mac 上默认为 128 字节,如果输入行长于此则增长),因此通常最好有一个由 getline() 管理的缓冲区,如果需要可以增长,并以所需的长度将实际输入字符串复制到其他地方。我使用 strdup() 复制行。

/* Read file and print the lines in reverse order */

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

int main(void)
{
    char **ptrs = 0;
    size_t numptrs = 0;
    size_t count = 0;
    char  *buffer = 0;
    size_t buflen = 0;

    while (getline(&buffer, &buflen, stdin) != -1)
    {
        if (count == numptrs)
        {
            size_t newnum = (numptrs + 2) * 2;
            void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));
            if (newptrs == 0)
            {
                fprintf(stderr, "Out of memory (%zu bytes requested)\n", newnum * sizeof(*ptrs));
                exit(1);
            }
            ptrs = newptrs;
            numptrs = newnum;
        }
        ptrs[count++] = strdup(buffer);
    }

    free(buffer);

    /* Print lines in reverse order */
    for (size_t i = count; i > 0; i--)
        fputs(ptrs[i-1], stdout);

    /* Free allocated memory */
    for (size_t i = 0; i < count; i++)
        free(ptrs[i]);
    free(ptrs);

    return 0;
}

很容易争辩说代码应该检查 strdup() 是否成功,如果不成功则采取适当的措施。释放已分配内存的代码应该在一个函数中——这也将使错误发生后的清理变得更容易。可以将代码修改为一个函数,该函数可用于处理列为命令行参数而不是标准输入的文件。

将此文本作为标准输入:

Because it messes up the order in which people normally read text.
> Why is top-posting such a bad thing?
>> Top-posting.
>>> What is the most annoying thing in e-mail?

程序产生输出:

>>> What is the most annoying thing in e-mail?
>> Top-posting.
> Why is top-posting such a bad thing?
Because it messes up the order in which people normally read text.