Valgrind 使用 asprintf 报告内存泄漏

Valgrind reports memory leak with asprintf

这里有一段代码,基于一个简单的结构

在套接字上写入HTTP响应
void write_response(request *req, response *resp, int socket) {
  char *raw_resp;
  int bytes = 0;

  asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);

  bytes += strlen(raw_resp);
  for (int i = 0; i < resp->header_count; i++) {
    asprintf(&raw_resp, "%s%s", raw_resp, resp->headers[i]);
    bytes += strlen(resp->headers[i]);
  }

  if (resp->content != NULL) {
    asprintf(&raw_resp, "%s\r\n", raw_resp);
    raw_resp = realloc(raw_resp, bytes + 2 + resp->content->size);
    memcpy(&raw_resp[strlen(raw_resp)], resp->content->data,
           resp->content->size);
    bytes += (resp->content->size + 2);
  }
  write(socket, raw_resp, bytes);
  free(raw_resp);
}

基本上,它首先添加 HTTP 请求行,然后是 headers,如果需要,最后是 body。

但是,valgrind 在前 2 个 asprintf 上报告 Invalid free() / delete / delete[] / realloc()18 bytes in 1 blocks are definitely lost in loss record 2 of 4(内存泄漏),但奇怪的是在第 3 个上没有。

我用的 asprintf 对吗?

手册页是这样说的:

The functions asprintf() .... allocate a string...

以后

This pointer should be passed to free(3) to release the allocated storage when it is no longer needed.

每次新调用都会导致新分配。您似乎没有释放之前分配的字符串。

您的代码没有 free asprintf 分配的所有字符串。鉴于您如何使用 asprintf 执行动态字符串连接,修复此问题有点麻烦。请注意,您也不处理内存分配失败。您可以使用 asprintf return 值来检测并更新 bytes,而无需额外的 strlen() 调用。

/* return the number of bytes written or -1 in case of error */
int write_response(request *req, response *resp, int socket) {
    char *raw_resp, *new_p;
    int bytes;

    bytes = asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
    if (bytes < 0)
        return -1;

    for (int i = 0; i < resp->header_count; i++) {
        bytes = asprintf(&new_p, "%s%s", raw_resp, resp->headers[i]);
        free(raw_resp);
        raw_resp = newp;
        if (bytes < 0)
            return -1;
    }

    if (resp->content != NULL) {
        bytes = asprintf(&new_p, "%s\r\n", raw_resp);
        free(raw_resp);
        raw_resp = newp;
        if (bytes < 0)
            return -1;
        new_p = realloc(raw_resp, bytes + resp->content->size);
        if (new_p == NULL) {
            free(raw_resp);
            return -1;
        }
        raw_resp = new_p;
        memcpy(raw_resp + bytes, resp->content->data, resp->content->size);
        bytes += resp->content->size;
    }
    bytes = write(socket, raw_resp, bytes);
    free(raw_resp);
    return bytes;
}

备注:

  • 使用asprintf执行字符串连接分配似乎效率低下,只需使用strlenreallocmemcpy.

  • asprintf() 是 non-standard,并非在所有平台上都可用。

  • 除非要求您发出对 write 的单个调用,否则单独编写内容可能更有效,以避免额外调用 realloc()大量内存。

  • 在初始阶段用 snprintfstrlen 计算 headers 的长度并分配 space 对于 headers 直接分配到完整大小,或者如果低于使用本地数组的合理阈值 (4K) 甚至不分配。

这是修改后的版本:

int write_response(request *req, response *resp, int socket) {
    char buffer[4096];
    char *raw_resp, *allocated = NULL;
    int bytes, pos;

    bytes = snprintf(NULL, 0, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
    for (int i = 0; i < resp->header_count; i++)
        bytes += strlen(resp->headers[i]);
    if (resp->content != NULL)
        bytes += 2 + resp->content->size;

    /* need an extra byte for `snprintf` null terminator 
       if no headers and no contents */
    if (bytes < sizeof(buffer)) {
        raw_resp = buffer;
    } else {
        raw_resp = allocated = malloc(bytes + 1): 
        if (raw_resp == NULL)
            return -1;
    }
    pos = snprintf(raw_resp, bytes, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
    for (int i = 0; i < resp->header_count; i++) {
        int len = strlen(resp->headers[i]);
        memcpy(raw_resp + pos, resp->headers[i], len);
        pos += len;
    }

    if (resp->content != NULL) {
        raw_resp[pos++] = '\r';
        raw_resp[pos++] = '\n';
        memcpy(raw_resp + pos, resp->content->data, resp->content->size);
        pos += resp->content->size;
    }
    bytes = write(socket, raw_resp, bytes);
    free(allocated);
    return bytes;
}

你缺少自由,你不需要 asprintf 除了第一次

可以是:

void write_response(request *req, response *resp, int socket) {
  char * buff;
  int len;

  len = asprintf(&buff, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
  if (len == -1)
    .. error management

  for (int i = 0; i < resp->header_count; i++) {
    size_t hlen = strlen(resp->headers[i]);

    buff = realloc(buff, len + hlen + 1);
    if (buff == NULL)
      ... error management
    strcpy(buff + len, resp->headers[i]);
    len += hlen;
  }

  if (resp->content != NULL) {
    buff = realloc(buff, len + 2 + resp->content->size);
    if (buff == NULL)
      ... error management
    buff[len] = '\r';
    buff[len + 1] = '\n';
    memcpy(len + 2, resp->content->data, resp->content->size);
    len += resp->content->size + 2;
  }
  if (write(socket, buff, len) != len)
    ... error management
  free(buff);
}