fwrite em C, precisando de uma explicação [RESOLVIDO]

1. fwrite em C, precisando de uma explicação [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 14/01/2022 - 21:40h

Boa Noite a todos.
Gostaria de entender melhor o fwrite e fiquei com uma dúvida!

A Struct abaixo representa apenas 1 Registro de um Banco de Dados
struct Registry {
char Nome [ 101],
Tel [ 16],
Notas[250001];
} Cadastro;

fwrite(&Cadastro, sizeof(struct Registry), 1, MyFile); // Gravo 1 Registro no arquivo
O Problema é que se preencho apenas Nome e Tel, deixando o Campo Notas vazio, fwrite salva no arquivo alocando o espaço para 250 Mil Caracteres desnecessáriamente!

Logo não vejo neste caso nenhuma vantagem em usar o fwrite para salvar um Banco de Dados em um arquivo!
Ou estou deixando de saber alguma coisa?


  


2. MELHOR RESPOSTA

Samuel Leonardo
SamL

(usa XUbuntu)

Enviado em 14/01/2022 - 23:58h

Mas é justamente esse o propósito do fwrite: escrever blocos de dados. E é isso que ele faz quando você passa os dois parâmetro "sizeof(struct Registry), 1" e é isso que essa função vai fazer, escrever 1 bloco do tamanho do struct Registry, mesmo que não tenha nenhuma inicialização em algum membro da struct.

Se quiser escrever apenas o que for iniciado, então, você deve usar outra abordagem que não seja escrever o bloco de memória inteiro.
Por exemplo, usar fprintf:
fprintf(arquivoAqui, "%s|%s\n", Cadastro.Nome, Cadastro.Tel);
//vai escrever o conteúdo de "Nome|Tel" separados por '|'

E então, ler com outra função como a fscanf:, ou fgets, ou qualquer outra que queira trabalhar.

3. Re: fwrite em C, precisando de uma explicação [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 16/01/2022 - 22:07h

O SamL já disse o que deveria ser dito a respeito do que foi perguntado, mas eu gostaria de acrescentar mais algumas informações que têm a ver com estilo e com uso consciente de memória.

1. Repetir o tipo da variável que foi usado na declaração é um mau hábito, pois torna a manutenção do código mais difícil (e ainda por cima é feio!). Sugiro que em vez de algo como
fwrite(&Cadastro, sizeof(struct Registry), 1, MyFile); 
você use o seguinte.
fwrite(&Cadastro, sizeof Cadastro, 1, MyFile); 

Ainda haverá alguma redundância, por ter de aparecer o nome da variável duas vezes, mas é um acoplamento muito mais direto e auto-documentado do que ter de lembrar o nome do tipo. Deixe o compilador fazer esse trabalho por você, e, de quebra, fique com um código mais limpo e mais manutenível.

2. Quando você usa strings com uma limitação máxima de tamanho, pode, em alguns casos, suprimir o terminador nulo, sobretudo quando tal string tiver exatamente o tamanho máximo previsto.

Num caso como o que você apresentou, no qual você trabalha com um registro com campos de tamanho fixo, eu suponho que você declarou, por exemplo, o campo nome com 101 elementos para ter 100 caracteres úteis, com espaço extra de um byte para o terminador. Suponho que os outros campos seguiram o mesmo padrão. Sendo assim, cada registro vai ter sempre pelo menos 3 bytes de espaço ocupado com algo que não representa nenhuma informação útil. Esses 3 bytes são pouco perto do tamanho total do registro (250118 bytes), mas poderiam representar um desperdício considerável se você tivesse muitos milhares de registros num banco de dados real.

Para trabalhar com strings sem o terminador quando tais strings atingem o tamanho máximo, você pode adaptar seu programa, ainda usando funções padronizadas. A tabela abaixo mostra alguns exemplos de transformações que podem ser feitas com essa finalidade.
//CAMPOS DO REGISTRO INCLUINDO TERMINADOR    //CAMPOS DO REGISTRO SEM TERMINADOR

strlen(reg.nome) strnlen(reg.nome, sizeof reg.nome)

strcmp(s, reg.nome) (
strncmp(s, reg.nome, sizeof reg.nome) ||
(unsigned char)s[sizeof reg.nome]
)

strcmp(reg.nome, s) (
strncmp(reg.nome, s, sizeof reg.nome) ||
-(int)((unsigned char)s[sizeof reg.nome])
)

strncmp(reg.nome, s, n) (
n<sizeof reg.nome? strncmp(reg.nome, s, n):
strncmp(reg.nome, s, sizeof reg.nome) ||
-(int)((unsigned char)s[sizeof reg.nome])
)

// strcat() é sempre perigosa; melhor // strncat() não é muito melhor, pois também não leva o
// evitá-la em qualquer contexto. // tamanho do destino em consideração.
strcat(s, reg.nome) strncat(s, reg.nome, sizeof reg.nome)

// strcat() é sempre perigosa; melhor // Variáveis lr, ls e left preexistentes e declaradas com
// evitá-la em qualquer contexto. // tipo size_t.
strcat(reg.nome, s) (
lr=strnlen(reg.nome, sizeof reg.nome), ls=strlen(s),
left=sizeof reg.nome-lr,
memmove(reg.nome+lr, left<ls? left: ls), reg.nome
)

strlcat(reg.nome, s, sizeof reg.nome) // Variável lr preexistente e declarada com tipo size_t.
(
lr=strlcat(reg.nome, s, sizeof reg.nome-1),
lr>=sizeof nome-1?
(reg.nome[lr]=s[sizeof reg.nome-lr], ++lr): lr
)

printf("%s", reg.nome) printf("%.*s", (int)sizeof reg.nome, reg.nome);

fgets(reg.nome, sizeof reg.nome, stdin) // Variável ch preexistente e declarada com tipo int.
// Variável lr preexistente e declarada com tipo size_t.
(
fgets(reg.nome, sizeof reg.nome, stdin)==NULL?
NULL:
(
(lr=strlen(reg.nome))==sizeof reg.nome-1 &&
(ch=fgetc(stdin))!=EOF && reg.nome[lr++]=ch,
reg.nome
)
)


3. O uso típico de fwrite() é com dados binários, não com dados majoritariamente em forma de texto, e sobretudo com texto que pode ter tamanhos muito variados. Algumas técnicas para esses casos incluem:

  • Trabalhar com arquivos de texto puro, separando campos do registro por uma quebra de linha (ou outro caráter que não possa de modo nenhum fazer parte dos dados de cada campo). A vantagem é que é fácil de ler com ferramentas externas (mas essa mesma característica pode ser um problema de segurança). As maiores desvantagens são a dificuldade de localizar um registro específico (deslocar-se pelo arquivo geralmente implica voltar ao início e varrer todos os registros, até chegar ao ponto desejado) e substituir um registro que tenha sido alterado.

    • Como caso particular, poder-se-ia usar uma biblioteca de I/O que passe os arquivos por um processo de compactação antes de escrever e descompactação ao ler. A zlib, por exemplo, tem funções como gzprintf() e gzgets() para substituir fprintf() e fgets(), entre outras. Entretanto, isso agrava ainda mais a questão de dificuldade de modificar um arquivo preexistente.

  • Trabalhar com registros de tamanho fixo para os campos com tamanho não muito grande, e transformar os campos com grande variação em algo que aponte para arquivos externos (um por registro ou por campo de registro, possivelmente com nomes na forma de UUID que são armazenados no arquivo principal, ou com ponteiros para um deslocamento e um tamanho dentro de um arquivão de blob, que pode conter os dados de vários registros). Alivia um pouco o problema dos registros mantidos no arquivo principal, facilitando, por exemplo, alterações em registros preexistentes, mas ainda deixa os dados do blob sujeitos às desvantagens apresentadas acima.

  • Usar uma biblioteca específica para armazenamento de dados em formatos arbitrários, tais como a Berkeley DB, GDBM ou TokyoCabinet, com um cadastro principal para os dados com pouca variação de tamanho e um possível segundo cadastro para os dados com tamanho muito variável, com a ligação entre os dois cadastros feita através de campos armazenados em cada registro do cadastro principal.

  • Usar um biblioteca de banco de dados pequeno, tal como SQLite (que pode ser uma forma de você se familiarizar com a forma de trabalhar que aparece na próxima alternativa).

  • Comunicar-se com um SGBD externo ao seu programa, tal como MariaDB, PostgreSQL ou algum SGBD comercial.


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


4. Re: fwrite em C, precisando de uma explicação [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 16/01/2022 - 23:51h

paulo1205 escreveu:
1. Repetir o tipo da variável que foi usado na declaração é um mau hábito, pois torna a manutenção do código mais difícil (e ainda por cima é feio!). Sugiro que em vez de algo como
fwrite(&Cadastro, sizeof(struct Registry), 1, MyFile); 
você use o seguinte.
fwrite(&Cadastro, sizeof Cadastro, 1, MyFile); 
Obrigado pela Dica

paulo1205 escreveu:
• Usar uma biblioteca específica para armazenamento de dados em formatos arbitrários, tais como a Berkeley DB, GDBM ou TokyoCabinet
• Usar um biblioteca de banco de dados pequeno, tal como SQLite
• Comunicar-se com um SGBD externo ao seu programa, tal como MariaDB, PostgreSQL ou algum SGBD comercial.

No momento eu estou querendo entender mesmo o funcionamento da criação de um database, talvez eu devesse estudar sim como a SQL grava os dados em arquivo, como ela acessa esses dados, vou ver se acho na Internet os Sources dos Databases que vc informa acima pra estudar como eles trabalham com arquivos.

Assim como pensei em estudar o Source do fwrite pra entender como ele faz, e até fazer uma modificação para que ele ignore qdo o registro está vazio.

Obrigado!






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts