【问题标题】:Coding a getline() implementation - Valgrind errors编写 getline() 实现 - Valgrind 错误
【发布时间】:2017-05-30 01:38:50
【问题描述】:

我必须重新编码getline() 函数的实现,但使用文件的文件描述符而不是FILE *。我只允许使用malloc()free(),以及最多25 行的5 个函数。 我认为我已经正确地完成了这个项目,尽管我是 C 的初学者并且我的代码可能不太好。

当我运行它时,它运行良好,但 valgrind 显示我 definetely lost x bytes, x 取决于文件长度和 READ_SIZE(在标题中定义的宏)。

根据 valgrind 的--leak-check=full,当我 malloc dest 时,str_realloc_cat 函数中存在内存泄漏。我试过但找不到我应该在哪里释放/做其他事情?

下面是我的代码:

char *get_next_line(const int fd)
{
  static char   *remaining = "";
  char          *buffer;
  ssize_t       cread;
  size_t       i;

  i = 0;
  if (remaining == NULL)
    return (NULL);
  if ((buffer = malloc(SOF(char) * READ_SIZE + 1)) == NULL ||
      (cread = read(fd, buffer, READ_SIZE)) < 0)
        return (NULL);
  buffer[cread] = 0;
  remaining = str_realloc_cat(remaining, buffer);
  while (remaining[i])
    {
      if (remaining[i] == 10)
        {
          remaining[i] = 0;
          buffer = str_create_cpy(remaining);
          remaining = remaining + i + 1;
          return (buffer);
        }
      i++;
    }
  return (check_eof(fd, buffer, remaining, cread));
}

char *str_realloc_cat(char *rem, char *buf)
{
  size_t i;
  size_t dest_i;
  char   *dest;

  i = (dest_i = 0);
  if ((dest = malloc(SOF(char) * (str_len(rem) + str_len(buf) + 1))) == NULL)
    return (NULL);
  while (rem[i])
    {
      dest[dest_i] = rem[i];
      dest_i++;
      i++;
    }
  i = 0;
  while (buf[i])
    {
      dest[dest_i] = buf[i];
      dest_i++;
      i++;
    }
  dest[dest_i] = 0;
  free(buf);
  return (dest);
}

char    *check_eof(const int fd, char *buffer, char *remaining, ssize_t cread)
{
  if (cread == 0)
    return (NULL);
  if (cread < READ_SIZE)
    {
      buffer = remaining;
      remaining = NULL;
      return (buffer);
    }
  return (get_next_line(fd));
}

char *str_create_cpy(const char *src)
{
  char *dest;
  size_t i;

  i = 0;
  if ((dest = malloc(sizeof(char) * str_len(src) + 1)) == NULL)
    return (NULL);
  while (src[i])
    {
      dest[i] = src[i];
      i++;
    }
  dest[i] = 0;
  return (dest);
}

int str_len(const char *str)
{
  size_t i;

  i = 0;
  while (str[i])
    i++;
  return (i);
}

如果您想测试,还有一个主要功能:

#define SOF(x) sizeof(x) // Why in the comments

int main(int ac, char **av)
{
  int  fd;
  char *s;

  UNUSED(ac);
  if (!av[1])
    return 1;
  fd = open(av[1], O_RDONLY);
  while ((s = get_next_line(fd)))
    {
      printf("%s\n", s);
      free(s);
    }
  close(fd);
}

【问题讨论】:

  • sizeof(char) 总是 1,不要用它污染你的代码。
  • 使用#define SOF(x) sizeof(x) - 未显示但推断 - 似乎有点毫无意义。每次通话可以节省 3 个字符的输入,但会造成混淆。不是一个好的权衡。
  • "某个 char 在另一个系统上超过 1 个字节(尽管可能性很小)" 发生这种情况的唯一方法是,如果 C 编译器符合标准。 sizeof(char) 根据 C 标准为 1,它从不依赖于系统。
  • if( !av[1] ) 是检查是否没有参数的错误方法。虽然av 数组是空终止的,但它只能保证av[ac] 将为空。 ac 在某些环境中可能为 0。相反,请使用if( ac &lt; 2 )。我还建议使用传统的argvargc 来表示“参数向量”和“参数计数”。最后从不使用无括号条件和循环,你只是要求一个很难找到的错误。如果要节省垂直空格,请切换到 1TBS 或Stroustrup
  • 琐事 — 使用 int i = 0; 并为自己节省一行。不完全是琐事——你有if (remaining[i] == 10),但应该有if (remaining[i] == '\n')

标签: c memory valgrind getline


【解决方案1】:

我很关心这条线:

remaining = remaining + i + 1;

remaining 是指向已分配缓冲区的指针。在这条线上,你销毁它,这意味着你不能free()它了。

【讨论】:

  • 这不是移动我的指针的唯一方法吗?我真的买不起一个可以复制之前从i + 1 开始的所有内容的函数。
  • @Greg01re 该技术的问题是分配给remaining 的内存无法释放。您需要按住原始指针才能释放它。
  • 那么我想我被无法释放的剩余内存卡住了?
  • @Greg01re 由于您担心内存泄漏和 valgrind 对您咆哮,您不应该留下松散的结局。
  • 我确实是,但是我花了很长时间才做到这一点,我将在大约 20 小时内完成它,而且他们不检查内存泄漏。考虑到这一点,我不想再重新开始工作——我认为这是可以理解的。我从没想过它来自这里,但现在很明显。
【解决方案2】:

你的算法不好:

  1. 您将缓冲区保存在分配内存中
  2. 您不使用结构来重新组合变量
  3. 你使用幻数remaining[i] == 10
  4. 您使用递归可以堆栈溢出return get_next_line(fd)。没关系,我没看清楚你有一个尾递归,只要确保对你的编译进行优化。
  5. 您有意大利面条代码。

你应该用更好的逻辑重写你的整个函数,首先使用这个结构:

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

#define GNL_SIZE 4096

struct gnl_context {
  char buffer[GNL_SIZE];
  size_t i;
  size_t read;
};

char *get_next_line_r(int fd, struct gnl_context *gnl_context);
char *get_next_line(int fd);

static char *read_buffer(struct gnl_context *gnl_context, char *str,
                         size_t *size) {
  size_t i = gnl_context->i;
  while (i < gnl_context->read && gnl_context->buffer[i] != '\n') {
    i++;
  }
  size_t j = i - gnl_context->i;

  char *ret = realloc(str, *size + j + 1);
  if (ret == NULL) {
    return NULL;
  }
  memcpy(ret + *size, gnl_context->buffer + gnl_context->i, j);
  *size += j;
  ret[*size] = '\0';
  gnl_context->i = i;

  return ret;
}

char *get_next_line_r(int fd, struct gnl_context *gnl_context) {
  char *str = NULL;
  size_t size = 0;
loop:
  if (gnl_context->i == gnl_context->read) {
    ssize_t ret = read(fd, gnl_context->buffer, GNL_SIZE);
    if (ret <= 0) {
      return str;
    }
    gnl_context->read = (size_t)ret;
    gnl_context->i = 0;
  }

  char *tmp = read_buffer(gnl_context, str, &size);
  if (tmp == NULL) {
    return str;
  }
  if (gnl_context->i != gnl_context->read) {
    gnl_context->i++;
    return tmp;
  }
  str = tmp;
  goto loop;
}

char *get_next_line(int fd) {
  static struct gnl_context gnl_context;
  return get_next_line_r(fd, &gnl_context);
}

int main(void) {
  char *str;
  while ((str = get_next_line(0)) != NULL) {
    printf("%s\n", str);
    free(str);
  }
}

【讨论】:

  • 好吧,我真的很感谢你在那里付出的努力和批评,但我更关心修复我自己的算法,尽管我快速浏览了你的算法,你的算法显然更好。我用 C 语言编写代码还不到 3 个月,所以我想我还有很多东西要学。不过我真的很感激,明天或在我完成这个项目后会更深入地研究一下,以了解更好的方法。
猜你喜欢
  • 2011-07-02
  • 1970-01-01
  • 1970-01-01
  • 2014-04-09
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多