简体   繁体   中英

Valgrind reports memory leak with asprintf

Here is a piece of code that writes an HTTP response on a socket based on a simple struct

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);
}

Basically, it adds the HTTP request line first, then the headers and finally if needed the body.

However, valgrind is reporting a Invalid free() / delete / delete[] / realloc() and 18 bytes in 1 blocks are definitely lost in loss record 2 of 4 (memory leaks) on the first 2 asprintf but weirdly not on the 3rd one.

Am I using asprintf right?

This is what the man page says:

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

and later

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

Every new call leads to a new allocation. You don't seem to free the previously allocated string .

Your code does not free all the strings allocated by asprintf . Given how you use asprintf to perform dynamic string concatenation, fixing this problem is a bit cumbersome. Note that you do not handle memory allocation failure either. You could use asprintf return value to detect that and update bytes without the need for an extra strlen() call.

/* 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;
}

Remarks:

  • Using asprintf to perform string concatenation with allocation seems inefficient, simply use strlen , realloc and memcpy .

  • asprintf() is non-standard, it is not be available on all platforms.

  • Unless you are requested to issue a single call to write , it might be more efficient to write the contents separately to avoid the extra call to realloc() for a potentially large amount of memory.

  • It might also be more efficient to compute the length of the headers in a initial phase with snprintf and strlen and allocate the space for the headers directly to the full size, or not even allocate if below a reasonable threshold (4K) using a local array.

Here is a modified version:

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;
}

You have missing frees, and you do not need asprintf except the first time

Can be:

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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM