【问题标题】:getline() in loop consuming constantly more memory in embedded linux循环中的getline()在嵌入式Linux中不断消耗更多内存
【发布时间】:2015-03-30 08:47:18
【问题描述】:

在周末运行应用程序后,我在早上注意到它被杀死了,因为没有留下任何内存。

在注释了部分代码后,我发现这个过程可能有问题,但我不知道为什么。

void readData (char * path) {

// Reading and parsing config from file
FILE * fp;
size_t len = 0;
size_t read;

char *begin, *end, line[100], data[100];

fp = fopen(path, "r");
if (fp != NULL)
{
    while ((read = getline(&line, &len, fp)) != -1) {
/*      begin = strstr(line, "\"data\":[")+8;
        end = strstr(begin, "]");
        strncpy(data, begin,strlen(begin) - strlen(end));
        data[strlen(begin) - strlen(end)] = 0;
*/
        int tmp;

//      sscanf(data,"%d,%d,%d,%d,%d,%d",&tmp,&ss.tensionRaw,&tmp,&tmp,&ss.depthRaw,&ss.speedRaw);
        ss.tension = cs.tensionCoeff * ss.tensionRaw;
        ss.speed   = cs.speedCoeff * ss.speedRaw;
        ss.depth   = cs.depthCoeff * ss.depthRaw;

/*      begin = strstr(line, "\"failedRequests\":\"")+18;
        end = strstr(begin, "\"");
        strncpy(data, begin,strlen(begin) - strlen(end));
        data[strlen(begin) - strlen(end)] = 0;
        ss.connectionOK = atoi(data);*/
    }
}
fclose(fp);
}

getline 会导致这样的问题吗? 我通过“顶部”监控应用程序的内存使用情况,每 15-20 秒它会增加 4K 左右。当我评论整个while循环时,它并没有增加。

【问题讨论】:

  • 首先检查linechar*的类型。
  • 需要逐行阅读吗?

标签: c linux memory-leaks getline


【解决方案1】:

getline 的手册强烈暗示行指针不是固定缓冲区,而是指向已分配字符串的指针,因为它声明

或者,在调用 getline() 之前,*lineptr 可以包含一个指针 到一个 ma​​lloc(3) 分配的缓冲区 *n 字节大小。如果缓冲区没有 大到足以容纳线,getline() 用 realloc(3) 调整它的大小, 根据需要更新 *lineptr 和 *n。

【讨论】:

【解决方案2】:

Thomas Dickey 的回答(由 alk 编辑)是正确的。

为了理智,这里有一个示例,说明如何使用 getline() 逐行正确读取文件:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Read the named file line by line.
 * Returns 0 if successful,
 * nonzero (== errno) error code otherwise.
*/
int line_by_line(const char *const filename)
{
    char   *line = NULL;
    size_t  size = 0;
    ssize_t len;
    long    linenum = 0L;
    FILE   *in;

    /* Invalid filename? */
    if (filename == NULL || *filename == '\0')
        return errno = EINVAL;

    /* Open file. If it fails, fopen() will set errno. */
    in = fopen(filename, "rt");
    if (in == NULL)
        return errno;

    /* Read loop. */
    while (1) {

        len = getline(&line, &size, in);
        if (len <= (ssize_t)0)
            break;

        linenum++;

        /* Note: if (strlen(line) < (size_t)len),
         *       the line contains embedded NUL (zero) bytes.
        */

        /*
         * Do something with the line ...
        */
        printf("%ld: %s", linenum, line);
    }

    /* Discard possibly used line buffer. Note: free(NULL) is safe. */
    free(line);

    /* I/O error? */
    if (!feof(in) || ferror(in)) {
        fclose(in);
        return errno = EIO;
    }
    if (fclose(in))
        return errno = EIO;

    /* All done successfully. */
    return 0;
}

但是,如果文件看起来不像实际上是面向行的数据;文件中只有一条有趣的记录。谁知道呢,它也可能被分成多行。

这是我在这个案例中使用的:

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

size_t read_file(const char *const filename, char **const dataptr, size_t *const sizeptr)
{
    FILE   *in;
    char   *data;
    size_t  size, more;
    size_t  have = 0;

    /* Invalid parameters? */
    if (filename == NULL || *filename == '\0' || dataptr == NULL || sizeptr == NULL) {
        errno = EINVAL;
        return (size_t)0;
    }

    /* If *dataptr is NULL, *sizeptr is assumed to be 0. */
    data = *dataptr;
    if (data != NULL)
        *sizeptr = 0;
    size = *sizeptr;

    /* Open file, read-only, in binary mode. If it fails, fopen() sets errno. */
    in = fopen(filename, "rb");
    if (in == NULL)
        return (size_t)0;

    while (1) {

        /* Need to grow the data buffer?
         * (The +1 accounts for the end-of-buffer NUL byte). */
        if (have + (size_t)1 >= size) {

            /* Start with a 8k buffer.
             * Double it in size for up to 1M,
             * then round to next full megabyte. */
            if (size < 8190)
                size = 8192;
            else
            if (size < 1048576)
                size *= 2;
            else
                size = (size | 524287) + 524289;

            /* Size too large (wrapped)? */
            if (size <= *sizeptr) {
                free(data);
                fclose(in);
                *dataptr = NULL;
                *sizeptr = 0;
                errno = ENOMEM;
                return (size_t)0;
            }

            /* Reallocate. */
            data = realloc(data, size);
            if (data == NULL) {
                free(data);
                fclose(in);
                *dataptr = NULL;
                *sizeptr = 0;
                errno = ENOMEM;
                return (size_t)0;
            }

            *dataptr = data;
            *sizeptr = size;
        }

        /* Read more data, but remember to leave room for the
         * terminating NUL byte. */
        more = fread(data + have, 1, size - have - 1, in);
        if (more > (size_t)0)
            have += more;
        else
            break;
    }

    /* I/O error? */
    if (!feof(in) || ferror(in)) {
        free(data);
        fclose(in);
        *dataptr = NULL;
        *sizeptr = 0;
        errno = EIO;
        return (size_t)0;
    }
    if (fclose(in)) {
        free(data);
        fclose(in);
        *dataptr = NULL;
        *sizeptr = 0;
        errno = EIO;
        return (size_t)0;
    }

    /* Append terminating NUL byte; we reserved space for it. */
    data[have] = '\0';

    /* Technically we shouldn't need to set errno. */
    errno = 0;
    return have;
}

struct state {
    double  tensionCoeff;
    double  speedCoeff;
    double  depthCoeff;
    double  tension;
    double  speed;
    double  depth;
    double  tensionRaw;
    double  speedRaw;
    double  depthRaw;
    long    connectionOk;
};
#define STATE_INITIALIZER { 0.0,0.0,0.0, 0.0,0.0,0.0, 0.0,0.0,0.0, 0L }

static char   *state_buffer_data = NULL;
static size_t  state_buffer_size = 0;

static void free_state_buffer(void)
{
    free(state_buffer_data);
    state_buffer_data = NULL;
    state_buffer_size = 0;
}

#define UPDATED_TRIPLET (1U << 0)
#define UPDATED_CONN_OK (1U << 1)

unsigned int update_state(const char *const filename, struct state *const current)
{
    unsigned int retval = 0U;
    char        *data;
    size_t       len;

    if (current == NULL) {
        errno = EINVAL;
        return 0U;
    }

    len = read_file(filename, &state_buffer_data, &state_buffer_size);
    if (!len) {
        if (errno == 0)
            errno = ENODATA;
        return 0U;
    }

    data = strstr(state_buffer_data, "\"failedRequests\":");
    if (data != NULL) {
        long conn_ok;
        if (sscanf(data+17, " %ld", &conn_ok) == 1) {
            current->connectionOk = conn_ok;
            retval |= UPDATED_CONN_OK;
        }
    }

    data = strstr(state_buffer_data, "\"data\":[");
    if (data != NULL) {
        double value[6];
        if (sscanf(data+8, " %lf , %lf , %lf , %lf , %lf , %lf ",
                   &(value[0]), &(value[1]), &(value[2]), 
                   &(value[3]), &(value[4]), &(value[5])) == 6) {
            current->tensionRaw = value[1];
            current->depthRaw = value[4];
            current->speedRaw = value[5];
            current->tension = current->tensionCoeff * current->tensionRaw;
            current->depth = current->depthCoeff * current->depthRaw;
            current->speed = current->speedCoeff * current->speedRaw;
            retval |= UPDATED_TRIPLET;
        }
    }

    /* All done. If the buffer is twice as large, or at least two megabytes larger
       than the amount of data we read, discard the buffer. */
    if ((state_buffer_size > len + len && len > 4095) ||
        state_buffer_size > len + 2048576)
        free_state_buffer();
    errno = 0;
    return retval;
}

即使数据分布在多行上,这也能正常工作。我可能会使用自定义函数来查找数据起点(strstr() 太死板,甚至这里或那里的额外空间都会把它扔掉)。

缓冲区在update_state() 调用之间重复使用,除非新数据远小于缓冲区大小,在这种情况下,缓冲区将被丢弃(并在下一次调用期间根据需要重新分配),以保持较低的内存开销。 (但是,请注意,大多数 C 库实现不会将内存返回给操作系统,除非分配最初足够大以使用内存映射而不是 sbrk()。)

update_state() 总是将 errno 设置为零,如果没有发生错误。它的返回值是UPDATED_ 值的位掩码;如果找到 failedRequests 记录,(retval &amp; UPDATED_CONN_OK) 为真,如果在参数结构中定位并更新了前六个数据字段中的三个数字字段,则(retval &amp; UPDATED_TRIPLET) 为真。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-01
    • 2020-05-20
    • 2021-08-09
    • 2015-01-16
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多