Анализировать пользовательский конфигурационный файл в C


Я должен анализировать пользовательский конфигурационный файл с такой структурой:

# Begin with core containers.

ID          = 0 # STX container
Name        = "" # No name is necessary for a STX container.
Flags       = {STX}
Attributes  = {Status=0}

Поэтому я написал следующий код для того, чтобы удалить эти комментарии (начинающиеся с #) и встроенные комментарии. Я не знаю, если я делаю это правильный путь, это только кажется (для меня, не слишком большой опыт) глупый способ сделать это с многих циклов while.

void NextToken() {
   // Let's begin taking comments away!
   FILE* Source = fopen("defs.dat", "r");
   char* Buffer = calloc(MAXBUFLEN, sizeof(char));
   while (fgets(Buffer, MAXBUFLEN, Source)) {
    // If line starts with '#' it is a comment, so it must be ignored. If it's a newine it just has to be skipped.
    if (Buffer[0] == 35 || Buffer[0] == '\n')
        continue;
    // Now we have to delete inline comments
    int Pos = 0;
    while (Pos < strlen(Buffer)) {
        if(Buffer[Pos] == '#') {
            Pos++;
            while(Buffer[Pos] != '\n')  {
                Pos++;
                continue;
            }
            continue;
        }
        //Cool! now just let's print the output.
        printf("%c", Buffer[Pos++]);
    }
   }
}

Максимальный размер буфера составляет 256 символов.



187
3
задан 8 марта 2018 в 02:03 Источник Поделиться
Комментарии
2 ответа

Вам не нужно calloc() с fgets()Он добавляет завершающий нулевой символ сам по себе: использование malloc() вместо. Также проверьте для выделения ошибок и, возможно, использовать осмысленные имена для Buffer (например line).

char* line= malloc(MAXBUFLEN, sizeof(char));
if (line == NULL) {
// Oops...
}

Также fopen() может удастся, тогда вы также должны проверить возвращаемое значение:

if (fopen(...) == NULL) {
// Oops
}

Вы должны переместить проверить на линии в игнор самостоятельным поясняющей функции, вот почему ты почувствовал необходимость добавить:

if (IsBlankOrComment(line))
continue;

Реализации IsBlankOrComment() вы должны избегать жестко константы, как 35вы должны добавить #define и использовать вместо символа его код ASCII:

#define COMMENT_DELIMITER '#'

bool IsBlankOrComment(char* line) {
char* trimmedLine = TrimWhiteSpaces(line);

return trimmedLine[0] != '\0'
&& trimmedLine[0] != COMMENT_DELIMITER;
}

Для возможной реализации вы можете прочитать этот пост так. Не забудьте включить stdbool.h если вы используете bool (вместо _Bool).

Вы теперь должны удалить комментарии, что лучше, чем strchr()? Не нужно вручную перебирать строки:

char* inlineCommentStart = strchr(line, COMMENT_DELIMITER);
if (inlineCommentStart != NULL) {
*inlineCommentStart = '\0';
}

Обратите внимание, что он также занимается линия с начальные и конечные пробелы, если вы не нуждаетесь в ней, то вы определенно должны пойти с Эдвардом прекрасная реализация (которая использует strpbrk()).

Чтобы напечатать строку (без комментариев), то вам просто необходимо:

puts(line);

Нет необходимости печатать символ за символом при поиске разделитель (кстати, не используйте printf() когда вам нужно распечатать только один символ!) Также обратите внимание, что вызов strlen() в цикле (скорее всего) крайне неэффективно, потому что, если компиляция не могу определить, что строка является константой в петле, он выдаст код для вызова strlen() для каждой итерации.

Не забудьте закрыть файл, Когда вы закончите:

fclose(stream);

И чтобы освободить выделенную память:

free(line);

В этом случае вы должны спросить себя, если вам нужно динамически выделенную память. Если MAXBUFSIZE это препроцессор постоянный, то вы можете напрямую объявить локальную переменную (проверьте на размер стека вопросы):

char line[MAXBUFSIZE];

Если приложение не многопоточное, и эта функция вызывается более чем один раз вы можете также рассмотреть, чтобы сделать его static. Наконец, не забудьте добавить const здесь и там, где это уместно.

2
ответ дан 8 марта 2018 в 02:03 Источник Поделиться

Я вижу некоторые вещи, которые я думаю, могут помочь вам улучшить ваш код.

Позволить пользователю указать входной и выходной файлы

Имя входного файла находится в настоящее время жестко, что, безусловно, значительно ограничивает полезность программы. Рассмотрите возможность использования argc и argv чтобы позволить пользователю указать имя файла в командной строке. Кроме того, было бы неплохо, чтобы иметь возможность отправлять вывод в файл.

Исправлена утечка памяти

Программа выделяет, но никогда не освобождает память, что приводит к утечке памяти из MAXBUFLEN байт каждый раз NextToken() называется. Если значение MAXBUFLEN действительно только 256, как было предложено в тексте, то это может быть простой, чтобы объявить его в стек и избежать явного выделения памяти.

Ликвидировать "магические числа"

Вместо жесткого кодирования постоянных 35 в одном месте и '#' в другом, это будет лучше использовать #define или const и назвать их.

Упростить код

Код использует printf("%c",...) где он мог бы просто использовать putchar() и исключить дополнительные затраты printf сканирование формат строки. Также петли, с тремя continue заявления не очень легко следовать. Вот что я вместо этого предлагаю:

void NextToken2(const char *filename) {
FILE* Source = fopen(filename, "r");
if (Source == NULL) {
fprintf(stderr, "Couldn't open file \"%s\"\n", filename);
return;
}
char Buffer[MAXBUFLEN];
char *ptr;
while (fgets(Buffer, MAXBUFLEN, Source)) {
if ((ptr = strpbrk(Buffer, "#\n")) != NULL) {
*ptr = '\0';
}
if (Buffer[0] != '\0') {
puts(Buffer);
}
}
fclose(Source);
}

2
ответ дан 8 марта 2018 в 04:03 Источник Поделиться