Valgrind сообщает об утечке памяти с помощью asprintf - PullRequest
0 голосов
/ 20 февраля 2019

Вот фрагмент кода, который записывает 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-запроса, затем заголовки и, наконец, при необходимости тело.

Однако valgrind сообщает о Invalid free() / delete / delete[] / realloc() и 18 bytes in 1 blocks are definitely lost in loss record 2 of 4 (утечках памяти) на первых 2 asprintf, но странно не на 3-м.

Использую ли я asprintf, верно?

Ответы [ 3 ]

0 голосов
/ 20 февраля 2019

Ваш код не free всех строк, выделенных asprintf.Учитывая, как вы используете asprintf для выполнения динамической конкатенации строк, исправление этой проблемы немного обременительно.Обратите внимание, что вы не обрабатываете ошибку выделения памяти.Вы можете использовать asprintf возвращаемое значение, чтобы обнаружить это и обновить 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 для выполнения конкатенации строк с выделением кажется неэффективным, просто используйте strlen, realloc и memcpy.

  • asprintf() не является стандартным, это небыть доступным на всех платформах.

  • Если вас не попросят сделать один вызов write, может быть более эффективно написать содержимое отдельно, чтобы избежать дополнительного вызова realloc() для потенциально большого объема памяти.

  • Также может быть более эффективно вычислить длину заголовков на начальном этапе с помощью snprintf и strlen и выделить пространстводля заголовков непосредственно в полный размер, или даже не выделять, если ниже разумного порога (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;
}
0 голосов
/ 20 февраля 2019

У вас нет свободных файлов, и вам не нужно 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);
}
0 голосов
/ 20 февраля 2019

Это то, что говорит man-страница:

Функции asprintf () .... выделяют строку ...

и позже

Этот указатель должен быть передан free (3) для освобождения выделенного хранилища, когда оно больше не требуется.

Каждый новый вызов приводит к новому выделению.Похоже, вы не освобождаете ранее выделенную строку.

...