Здесь есть как минимум одна утечка памяти:
opts.files = malloc(sizeof(NULL));
opts.files = NULL;
Вы выделяете память для размера NULL
, который не имеет смысла, так как NULL
может быть определен как 0
или ((void*)0)
, которые могут иметь разные размеры, и вы немедленно перезаписываете этот указатель с помощью opts.files = NULL
, делая выделенный блок недоступен. Вы должны выделить место для одного указателя и инициализировать его как NULL
.
В Options_cleanup_mem
есть еще одна утечка: вы забыли освободить массив, на который указывает opts->files
.
Размер, переданный в tmp = realloc(opts->files, num_files + 1);
, неверен, вы должны умножить количество элементов на размер элемента sizeof(opts->files[0])
. Кроме того, новое число элементов - num_files + 2
, включая новое имя файла и нулевой терминатор.
Строка free(opts->files[num_files]);
является избыточной в Options_append_filename
, поскольку opts->files[num_files]
является указателем NULL
.
Вы должны выйти из программы во всех случаях ошибки в parse_cmdline()
.
Вы должны освободить выделенную память перед возвратом из main()
с помощью Options_cleanup_mem(&opts);
, чтобы valgrind не диагностировал несвободную память.
Вот модифицированная версия:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
typedef struct {
bool help_flag;
bool version_flag;
char **files;
} Options;
Options Options_new(void) {
Options opts;
opts.help_flag = false;
opts.version_flag = false;
opts.files = malloc(sizeof(opts.files[0]));
opts.files[0] = NULL;
return opts;
}
void Options_cleanup_mem(Options *opts) {
int i = 0;
if (opts->files) {
while (opts->files[i] != NULL)
free(opts->files[i++]);
free(opts->files);
}
}
int Options_append_filename(Options *opts, const char *filename) {
int num_files = 0;
char **tmp;
while (opts->files[num_files] != NULL)
num_files++;
tmp = realloc(opts->files, sizeof(opts->files[0]) * (num_files + 2));
/* Handle case where realloc can't allocate memory */
if (tmp == NULL) {
return -1;
}
opts->files = tmp;
opts->files[num_files] = malloc(strlen(filename) + 1);
strcpy(opts->files[num_files], filename);
opts->files[num_files + 1] = NULL;
return 0;
}
Options parse_cmdline(int argc, char **argv) {
Options opts = Options_new();
for (int a = 0; a < argc; a++) {
if (argv[a][0] == '-') {
char *longopt = argv[a];
if (!strcmp(longopt, "--help")) {
opts.help_flag = true;
} else if (!strcmp(longopt, "--version")) {
opts.version_flag = true;
} else {
fprintf(stderr, "Unkown option\n");
Options_cleanup_mem(&opts);
exit(1);
}
} else {
if (Options_append_filename(&opts, argv[a]) != 0) {
fprintf(stderr, "Error processing arguments\n");
Options_cleanup_mem(&opts);
exit(1);
}
}
}
return opts;
}
int main(int argc, char **argv) {
Options opts = parse_cmdline(argc, argv);
if (opts.help_flag) {
printf("Help message\n");
return 2;
}
if (opts.version_flag) {
printf("Version message\n");
return 2;
}
int i = 0;
while (opts.files[i] != NULL)
i++;
for (int a = 0; a < i; a++)
printf("opts.files[%d] is: '%s'\n", a, opts.files[a]);
Options_cleanup_mem(&opts);
return 0;
}
Примечания:
- Трудно обрабатывать ошибки выделения памяти, когда вы возвращаете структуру
Options
по значению. Options_new
должен взять указатель и вернуть статус ошибки.
- Вы проверяете на наличие ошибки
realloc()
, но не на другие ошибки malloc()
.
- Вы должны хранить размер массива внутри структуры
Options
, чтобы избежать его повторного вычисления везде.