Gerenciador de lista em C com suporte a estrutura de dados desconhecida [RESOLVIDO]

1. Gerenciador de lista em C com suporte a estrutura de dados desconhecida [RESOLVIDO]


oxidante

(usa Debian)

Enviado em 01/05/2018 - 22:03h

Criei um gerenciador de lista (fila) em C que oferece suporte a estruturas de dados desconhecidas, as quais são definidas pelo programador durante o desenvolvimento. Internamente, a fila usa o tipo void* para armazenar o conteúdo da estrutura que, por sua vez, é armazenada dentro de cada struct Node. Já implementei algumas funções básicas como add(), del(), get(), first() e last().

O problema que estou tendo é com a função set(), que escreve um valor para a um campo específico da estrutura. Como o gerenciador não tem acesso aos campos da estrutura, o programador precisa passar manualmente o offset de cada campo para set(), o que se torna inconveniente para ele, já que o compilador poderá realizar alinhamentos de memória durante a geração do executável e modificar os offsets de maneira imprevisível.

Existe alguma forma no C para contornar esse problema, de modo que o gerenciador continue suportando estruturas desconhecidas?
main.c:

/* Estrutura definida pelo programador. */
struct Pessoa {
char nome[11];
int idade;
char sexo;
float altura;
};

/* Eis aqui o problemão!
O programador precisará definir os offsets de cada campo
para poder usar a fila. */
#define OFF_NOME 0
#define OFF_IDADE 11
#define OFF_SEXO OFF_IDADE+sizeof(int)
#define OFF_ALTURA sizeof(struct Pessoa)-sizeof(float)

int main() {
struct Node *fila = 0;
struct Pessoa pessoa;

// cria a fila passando o tamanho da estrutura
fila = fila_init(sizeof(struct Pessoa));
if(!fila)
{
puts("Erro na criação da fila!");
return -1;
}

// adiciona o primeiro nó
strncpy(pessoa.nome, "Ana", sizeof(pessoa.nome)-1);
pessoa.idade = 5;
fila_adiciona(fila, (void*)&pessoa);

// escreve novos valores no nó que se encontra no índice 0
fila_set_str (fila, 0, "Amanda", 10, OFF_NOME);
fila_set_int (fila, 0, 50, OFF_IDADE);

//....
return 0;
}

fila.c:

/* Estrutura Node usada internamente pelo gerenciador */
struct Node {
void *st;
int st_size;
struct Node *prox;
};

int fila_set_str(struct Node *fila, int index, const char *s, int maxlen, int campo_offset)
{
unsigned char *pbST;
char *pChar;
pbST = (unsigned char*)fila_get(fila, index);
if(pbST)
{
pChar = (char*)(pbST+campo_offset);
strncpy(pChar, s, maxlen);
}
}

void fila_set_int(struct Node *fila, int index, int valor, int campo_offset)
{
unsigned char *pbST;
int *pInt;
pbST = (unsigned char*)fila_get(fila, index);
if(pbST)
{
pInt = (int*)(pbST+campo_offset);
*pInt = valor;
}
}



  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/05/2018 - 12:07h

O Phoemur foi muito feliz ao salientar que C não é a melhor linguagem para fazer esse tipo de coisa porque, embora seja possível fazê-las, vai dar tanto trabalho, e o código vai ficar tão cheio de artifícios (macros e conversões de tipos de ponteiros pra todo lado) que tende a ficar terrível de se ler e manter.

Então, eu realmente concordo que é melhor aprender a usar C++ ou alguma outra linguagem com suporte a programação genérica se você quiser poder trabalhar com programação genérica de um modo que seja eficiente para sua produtividade.


Um ponto que eu gostaria de acrescentar é que você chamou o tipo de fila, deu a entender que ele é implementado por meio de uma lista (OK), mas está preocupado com uma função que trata a fila com uma cara parecida com a de um array, ao permitir acesso a um elemento no meio da fila. Isso é um tanto atípico, e eu não estou certo de que sua API de fila deveria oferecer tal função.

Se, em vez de fila, você dissesse que o seu tipo é uma lista, aí sim tal função poderia fazer um pouco mais de sentido, porque a lista, ao contrário da fila, não implica que as operações terão de ser feitas apenas nos extremos, mas podem ocorrer em qualquer elemento. Mesmo assim, não sei se essa abordagem com jeito de array seria apropriada para a API.

Ainda sobre API, possivelmente seria interessante você ter tipos diferentes para representar nós (como os de uma lista), e a fila em si, que poderia guardar, de forma agregada, informações como ponteiros distintos para o início e o fim da fila, comprimento atual e outras informações que você julgasse interessantes.



Tendo feito essas colocações (que acho sinceramente que você deveria considerar com carinho), pergunto: você precisa que as funções que você quer implementar tenham exatamente a forma que você mostrou? Porque se você mudar um pouco essa forma, as coisas podem ficar bem mais fáceis.

Eu acho que, se eu tivesse de implementar uma lista genérica como a sua, mais cedo ou mais tarde eu acabaria criando front-ends para cada especialização da lista. Os tipos especializados continuariam usando a lista genérica como back-end, mas permitiriam meios mais convenientes de ter acesso aos dados de cada tipo específico.

Para começar, poderíamos criar dentro de fila.h uma macro que facilitasse especializações, num jeito mais ou menos como vai abaixo (ATENÇÃO: não testei nada desse código).

#define QUEUE_SPECIALIZATION(data_type, new_type_name) \
/* Define o tipo “new_type_name” como uma estrutura anônima contendo um único */ \
/* campo, de modo que um ponteiro para a estrutura coincida numericamente com */ \
/* um ponteiro para o campo, permitindo converter um no outro facilmente. */ \
typedef struct { Node n; } new_type_name; \
\
/* Inicializa um objeto do tipo “new_type_name”. Note a conversão de tipo de ‘Node *’ */ \
/* para ‘new_type_name *’, que se vale da coincidência numérica da declaração acima. */ \
/* O operador de concatenação do preprocessador, “##“, é usado para gerar uma função */ \
/* cujo nome será “new_type_name_init”. Note ainda que, como esse será um tipo para */ \
/* dados específicos (“data_type”), a função de inicialização não precisa receber ar- */ \
/* gumentos, mas usa a designação de tipo de dado informada na macro. */ \
new_type_name *new_type_name##_init(void){ \
return (new_type_name *)fila_init(sizeof(data_type)); \
} \
\
/* Função para adicionar um novo elemento na fila especializada. Note que ele recebe */ \
/* um ponteiro para o tipo especializado, não para a lista genérica, mas chama a função */ \
/* genérica para realizar a adição. Note também que: */ \
/* (1) fila_add() deve sempre fazer uma cópia do objeto que recebe, a fim de evitar */ \
/* problemas caso o objeto original tenha um tempo de vida menor que o da fila; */ \
/* (2) o segundo argumento de fila_add() deve ser do tipo ‘const void *’ (e isso, de */ \
/* certo modo, reforça a necessidade de fazer cópia do dado); */ \
/* (3) não é necessária a conversão explícita do ponteiro para o dado para ‘const */ \
/* void *’, pois o C especifica que tal conversão é sempre possível e válida; */ \
/* (4) o tipo de retorno de fila_add() é int, a fim de indicar se a adição foi feita */ \
/* com sucesso ou se falhou, e esse valor é reencaminhado por esta função. */ \
int new_type_name##_add(new_type_name *start, const data_type *data){ \
return fila_add(&start->n, data); \
} \
\
/* Assumindo que fila_get() retorne o valor do campo ‘st’ no registro do tipo ‘Node’, */ \
/* esta função converte tal valor, que é um ponteiro do tipo ‘void *’, num ponteiro */ \
/* do tipo ‘data_type *’. Note que não é necessária conversão explícita, porque con- */ \
/* versões de “void *’ para qualquer outro tipo de ponteiro são automáticas em C. */ \
data_type *new_type_name##_get(new_type_name *start, size_t index){ \
return fila_get(&start->n, index); \
} \
\
/* Implementa especializações das demais funções de manipulação da fila, que você */ \
/* mesmo elencou (e.g. fila_first(), fila_last(), fila_del() etc.), seguindo o modelo */ \
/* de transformações usado nas funções acima. */ \
\
/* Uma versão um pouco mais segura da função ..._get() mostrada acima, que retorna */ \
/* apenas uma cópia do dado, em lugar de um ponteiro diretamente para ele. */ \
data_type new_type_name##_get_copy(new_type_name *start, size_t index){ \
return *new_type_name##_get(start, index); \
} \
/* Fim da macro. */


(Continua mais tarde...)

3. Re: Gerenciador de lista em C com suporte a estrutura de dados desconhecida [RESOLVIDO]

Fernando
phoemur

(usa Debian)

Enviado em 03/05/2018 - 00:03h

Cara, saber o tanto que é fácil fazer isso que você está pedindo usando C++ com templates até desanima de fazer em C puro...
Mas daqui a pouco um programador em C mais "roots" aparece aqui pra te ajudar.
Flw


4. Re: Gerenciador de lista em C com suporte a estrutura de dados desconhecida [RESOLVIDO]


oxidante

(usa Debian)

Enviado em 05/05/2018 - 05:52h

Paulo, obrigado pela sugestão do uso de macro, creio que era isso que faltava! Vou estudar melhor para poder adaptá-lo ao meu código. O container que eu havia criado era inicialmente uma fila de fato. Depois atualizei para suportar novas funções e estrutura personalizada e acabou se tornando um container qualquer. Você tem razão em me alertar que não é apropriado misturar os termos. Agradeço mais uma vez pela dica!

phoemur, to ciente de que o C++ já vem com esse recurso. É que estou criando uma lib para uso pessoal e queria que fosse em C mesmo.






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts