Configuração via arquivos INI, JSON ou YAML
1. Por que usar arquivos de configuração em C?
1.1. Separar código de dados: parâmetros mutáveis sem recompilação
Em aplicações C tradicionais, parâmetros como endereços de servidores, portas de conexão ou níveis de log ficam hardcoded no código-fonte. Qualquer alteração exige recompilação completa do projeto. Arquivos de configuração resolvem esse problema permitindo que valores sejam alterados externamente, sem tocar no código compilado.
1.2. Comparação com variáveis de ambiente e argumentos de linha de comando
Variáveis de ambiente (getenv) e argumentos de linha de comando (argc/argv) também permitem parametrização externa, mas possuem limitações: variáveis de ambiente poluem o escopo global e não suportam hierarquia; argumentos de linha de comando tornam-se inviáveis com muitos parâmetros. Arquivos de configuração oferecem estrutura, documentação embutida (comentários) e persistência entre execuções.
1.3. Escolha do formato: quando usar INI, JSON ou YAML
- INI: formato mais simples e rápido de parsear. Ideal para configurações planas com poucas seções. Excelente em sistemas embarcados.
- JSON: suporta estruturas aninhadas (arrays, objetos). Padrão para APIs web e ferramentas modernas. Parseamento moderadamente rápido.
- YAML: máxima legibilidade humana com indentação significativa. Suporta âncoras e referências. Parseamento mais lento, mas ideal para configurações complexas.
2. Parsing de arquivos INI (formato clássico)
2.1. Estrutura típica
Arquivos INI organizam-se em seções [secao], com pares chave=valor e comentários iniciados por ; ou #.
; config.ini - Configuração de servidor
[server]
host=192.168.1.100
port=8080
timeout=30
[logging]
level=info
file=/var/log/app.log
2.2. Implementação manual vs. bibliotecas
Implementar um parser INI manualmente é viável para casos simples, mas bibliotecas como inih (header-only, ~100 linhas) e libconfuse oferecem robustez, suporte a escapes e tratamento de erros.
2.3. Exemplo prático com inih
#include <stdio.h>
#include <string.h>
#include "ini.h"
typedef struct {
char host[256];
int port;
int timeout;
} server_config_t;
static int handler(void* user, const char* section, const char* name,
const char* value) {
server_config_t* cfg = (server_config_t*)user;
if (strcmp(section, "server") == 0) {
if (strcmp(name, "host") == 0)
snprintf(cfg->host, sizeof(cfg->host), "%s", value);
else if (strcmp(name, "port") == 0)
cfg->port = atoi(value);
else if (strcmp(name, "timeout") == 0)
cfg->timeout = atoi(value);
}
return 1;
}
int main() {
server_config_t cfg = { .port = 0, .timeout = 0 };
if (ini_parse("config.ini", handler, &cfg) < 0) {
printf("Erro ao ler config.ini\n");
return 1;
}
printf("Host: %s, Porta: %d, Timeout: %d\n", cfg.host, cfg.port, cfg.timeout);
return 0;
}
3. Parsing de arquivos JSON (formato moderno e aninhado)
3.1. Modelo de dados JSON
JSON representa dados como objetos ({}), arrays ([]), strings, números, booleanos e null. Permite aninhamento profundo, ideal para configurações hierárquicas.
{
"logger": {
"level": "debug",
"output": "/var/log/app.log",
"format": "json",
"rotation": {
"max_size_mb": 100,
"max_files": 5
}
}
}
3.2. Bibliotecas populares em C
- cJSON: leve, header-only, sem dependências externas. Ideal para projetos pequenos.
- Jansson: madura, com suporte a schema e encoding. Mais completa.
- parson: extremamente leve (~500 linhas), focada em parseamento básico.
3.3. Exemplo prático com cJSON
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
typedef struct {
char level[16];
char output[256];
char format[16];
} logger_config_t;
int load_json_config(const char* filename, logger_config_t* cfg) {
FILE* f = fopen(filename, "r");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
char* data = malloc(len + 1);
fread(data, 1, len, f);
data[len] = '\0';
fclose(f);
cJSON* json = cJSON_Parse(data);
free(data);
if (!json) return -2;
cJSON* logger = cJSON_GetObjectItem(json, "logger");
if (logger) {
cJSON* item;
if ((item = cJSON_GetObjectItem(logger, "level")))
snprintf(cfg->level, sizeof(cfg->level), "%s", item->valuestring);
if ((item = cJSON_GetObjectItem(logger, "output")))
snprintf(cfg->output, sizeof(cfg->output), "%s", item->valuestring);
if ((item = cJSON_GetObjectItem(logger, "format")))
snprintf(cfg->format, sizeof(cfg->format), "%s", item->valuestring);
}
cJSON_Delete(json);
return 0;
}
int main() {
logger_config_t cfg = {0};
if (load_json_config("config.json", &cfg) != 0) {
printf("Erro ao carregar config.json\n");
return 1;
}
printf("Logger: level=%s, output=%s, format=%s\n",
cfg.level, cfg.output, cfg.format);
return 0;
}
4. Parsing de arquivos YAML (formato hierárquico legível)
4.1. Características do YAML
YAML usa indentação (espaços) para definir hierarquia, suporta listas com -, dicionários com chave: valor, âncoras (&) e aliases (*). É o formato mais legível para configurações complexas.
plugins:
- name: auth
path: /usr/lib/plugins/auth.so
params:
timeout: 30
retries: 3
- name: cache
path: /usr/lib/plugins/cache.so
params:
max_entries: 1000
4.2. Bibliotecas em C
- libyaml: parser de baixo nível, evento-driven. Flexível, mas requer mais código.
- cyaml: wrapper de alto nível sobre libyaml, com mapeamento direto para structs C.
4.3. Exemplo prático com libyaml
#include <stdio.h>
#include <yaml.h>
void parse_yaml_config(const char* filename) {
FILE* f = fopen(filename, "r");
if (!f) return;
yaml_parser_t parser;
yaml_event_t event;
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, f);
do {
yaml_parser_parse(&parser, &event);
if (event.type == YAML_SCALAR_EVENT) {
printf("Valor: %s\n", event.data.scalar.value);
}
yaml_event_delete(&event);
} while (event.type != YAML_STREAM_END_EVENT);
yaml_parser_delete(&parser);
fclose(f);
}
5. Abstração unificada: interface de configuração genérica
5.1. Definição de struct comum
typedef struct {
char host[256];
int port;
int timeout;
char log_level[16];
char log_file[256];
} config_t;
5.2. Funções de carga unificadas
config_t load_config_ini(const char* file);
config_t load_config_json(const char* file);
config_t load_config_yaml(const char* file);
config_t load_config(const char* file) {
const char* ext = strrchr(file, '.');
if (!ext) return (config_t){0};
if (strcmp(ext, ".ini") == 0) return load_config_ini(file);
if (strcmp(ext, ".json") == 0) return load_config_json(file);
if (strcmp(ext, ".yaml") == 0 || strcmp(ext, ".yml") == 0)
return load_config_yaml(file);
return (config_t){0};
}
5.3. Vantagens da abstração
Permite trocar o formato de configuração sem alterar a lógica de negócio. Pode-se implementar fallback: tentar JSON, depois YAML, depois INI.
6. Tratamento de erros e validação
6.1. Parsing mal-sucedido
Sempre verificar retornos de funções de parsing. Arquivo ausente, sintaxe inválida ou campos obrigatórios faltando devem ser tratados explicitamente.
6.2. Validação de tipos e limites
int validate_port(int port) {
return (port >= 1 && port <= 65535) ? 1 : 0;
}
int validate_timeout(int timeout) {
return (timeout > 0 && timeout <= 3600) ? 1 : 0;
}
6.3. Mensagens de erro amigáveis
fprintf(stderr, "Erro na linha %d, coluna %d: valor inválido para 'port'\n",
error_line, error_col);
7. Considerações de desempenho e portabilidade
7.1. Overhead de parsing
- INI: mais rápido (~1-5 µs para arquivos pequenos)
- JSON: moderado (~10-50 µs)
- YAML: mais lento (~50-200 µs) devido à complexidade sintática
7.2. Uso em sistemas embarcados
Preferir INI ou JSON com alocação estática. Evitar alocações dinâmicas frequentes. Bibliotecas como inih e parson são ideais por seu tamanho reduzido.
7.3. Boas práticas
- Cachear configuração em memória após leitura
- Implementar recarga via sinal SIGHUP:
void sig_handler(int signo) {
if (signo == SIGHUP) reload_config();
}
Referências
- inih: INI file parser in C — Biblioteca header-only para parsing de arquivos INI, com exemplos de uso e documentação completa.
- cJSON: Ultralightweight JSON parser in ANSI C — Parser JSON leve e sem dependências, amplamente utilizado em projetos C.
- libyaml: YAML parser and emitter library — Biblioteca oficial de parsing YAML em C, com suporte a eventos e documentos.
- Jansson: C library for encoding, decoding and manipulating JSON data — Biblioteca JSON madura com suporte a schema e encoding/decoding bidirecional.
- libconfuse: Configuration file parser library — Biblioteca para parsing de arquivos de configuração no estilo INI, com suporte a includes e tipos variados.
- parson: Lightweight JSON library written in C — Biblioteca JSON extremamente leve (~500 linhas), focada em simplicidade e portabilidade.
- cyaml: C YAML library with struct mapping — Wrapper de alto nível sobre libyaml que mapeia diretamente documentos YAML para structs C.