首页 > 解决方案 > 我应该如何修复这个有趣的 getdelim / getline(动态内存分配)错误?

问题描述

我有这个 C 任务我在这个特定的点上有点挣扎。我有一些 C 语言背景,但指针和动态内存管理仍然让我很困惑。

作业要求我们编写一个程序来模拟 UNIX 中“uniq”命令/过滤器的行为。

但我遇到的问题是 C 库函数 getline 或 getdelim (我们需要根据实现规范使用这些函数)。

根据规范,用户输入可能包含任意数量的行,并且每行可能是任意长度(在编译时未知)。

问题是,while 循环的以下行 while (cap = getdelim(stream.linesArray, size, '\n', stdin))

当我这样离开时,它会以某种方式编译和“工作”。我的意思是,当我执行程序时,我每行输入任意数量的任意长度的行并且程序不会崩溃 - 但它会一直循环,除非我停止程序执行(这些行是否正确存储在“ char **linesArray; ”中是一个不同的故事,我不确定。

我想做的是类似于 while ((cap = getdelim(stream.linesArray, size, '\n', stdin)) && (cap != -1))

这样当 getdelim 没有在某行读取任何字符时(除了 EOF 或 \n) - 也就是用户第一次输入空行时 - 程序将停止从标准输入中获取更多行。(然后通过 getdelim 打印存储在 stream.linesArray 中的行)。

问题是,当我执行程序时,如果我进行了上面提到的更改,程序会给我“分段错误”,坦率地说,我不知道为什么以及如何解决这个问题(我试图对此做一些事情多次无济于事)。

以供参考:

https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html

https://en.cppreference.com/w/c/experimental/dynamic/getline

http://man7.org/linux/man-pages/man3/getline.3.html

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

#define DEFAULT_SIZE 20

typedef unsigned long long int ull_int;

typedef struct uniqStream
{
    char **linesArray;
    ull_int lineIndex;

}   uniq;

int main()
{
    uniq stream = { malloc(DEFAULT_SIZE * sizeof(char)), 0 };

    ull_int cap, i = 0;  
    size_t *size = 0;

    while ((cap = getdelim(stream.linesArray, size, '\n', stdin))) //&& (cap != -1))
    {
        stream.lineIndex = i;

        //if (cap == -1) { break; }
        //print("%s", stream.linesArray[i]);    

        ++i;
        if (i == sizeof(stream.linesArray))
        {
            stream.linesArray = realloc(stream.linesArray, (2 * sizeof(stream.linesArray)));
        }
    }

    ull_int j;
    for (j = 0; j < i; ++j)
    {
        printf("%s\n", stream.linesArray[j]);    
    }

    free(stream.linesArray);
    return 0;
}

标签: csegmentation-faultuser-inputdynamic-memory-allocation

解决方案


好的,所以意图很明确 - 用于getdelim将行存储在数组中。getline本身使用动态分配。手册上说得很清楚:

getline() 从流中读取整行,将包含文本的缓冲区的地址存储到 *lineptr 中。缓冲区以空值结尾并包括换行符(如果找到)。

getline()将缓冲区的地址存储到 *lineptr”。所以lineptr必须是指向char *变量的有效指针(读取两次)。

*lineptr 和 *n 将被更新以分别反映缓冲区地址和分配的大小。

n需要是指向size_t变量的有效(!)指针,以便函数可以更新它。

还要注意lineptr缓冲区:

即使 getline() 失败,用户程序也应释放此缓冲区。

那么我们该怎么办?我们需要一个指向字符串数组的指针数组。因为我不喜欢成为三星级程序员,所以我使用结构。我稍微修改了您的代码,添加了一些检查。对不起,我不喜欢 typedef,所以我不使用它们。重命名uniqstruct lines_s

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

struct line_s {
    char *line;
    size_t len;
};

struct lines_s {
    struct line_s *lines;
    size_t cnt;
};


int main() {
    struct lines_s lines = { NULL, 0 };

    // loop breaks on error of feof(stdin)
    while (1) {

        char *line = NULL;
        size_t size = 0;
        // we pass a pointer to a `char*` variable
        // and a pointer to `size_t` variable
        // `getdelim` will update the variables inside it
        // the initial values are NULL and 0
        ssize_t ret = getdelim(&line, &size, '\n', stdin);
        if (ret < 0) {
            // check for EOF
            if (feof(stdin)) {
                // EOF found - break
                break;
            }
            fprintf(stderr, "getdelim error %zd!\n", ret);
            abort();
        }

        // new line was read - add it to out container "lines"
        // always handle realloc separately
        void *ptr = realloc(lines.lines, sizeof(*lines.lines) * (lines.cnt + 1));
        if (ptr == NULL) {
            // note that lines.lines is still a valid pointer here
            fprintf(stderr, "Out of memory\n");
            abort();
        }
        lines.lines = ptr;
        lines.lines[lines.cnt].line = line;
        lines.lines[lines.cnt].len = size;
        lines.cnt += 1;


        // break if the line is "stop"
        if (strcmp("stop\n", lines.lines[lines.cnt - 1].line) == 0) {
            break;
        }
    }

    // iterate over lines
    for (size_t i = 0; i < lines.cnt; ++i) {
        // note that the line has a newline in it
        // so no additional is needed in this printf
        printf("line %zu is %s", i, lines.lines[i].line);
    }

    // getdelim returns dynamically allocated strings
    // we need to free them
    for (size_t i = 0; i < lines.cnt; ++i) {
         free(lines.lines[i].line);
    }
    free(lines.lines);
}

对于这样的输入:

 line1 line1
 line2 line2
 stop

将输出:

 line 0 is line1 line1
 line 1 is line2 line2
 line 2 is stop 

onlinegdb上测试。

笔记:

  1. if (i == sizeof(stream.linesArray)) sizeof不会神奇地存储数组的大小。sizeof(stream.linesArray)is justsizeof(char**)只是一个指针的大小。它通常是 4 或 8 个字节,具体取决于 32 位还是 64 位架构。

  2. uniq stream = { malloc(DEFAULT_SIZE * sizeof(char)),-stream.linesArray是一个char**变量。所以如果你想要一个指针数组char,你应该为指针分配内存malloc(DEFAULT_SIZE * sizeof(char*))

  3. typedef unsigned long long int ull_int;size_t表示数组大小或 sizeof(variable) 的类型。有时在ssize_tposix api 中用于返回大小和错误状态。使用这些变量,无需键入unsigned long long.
  4. ull_int cap cap = getdelim- cap 是无符号的,它永远不会是cap != 1

推荐阅读