Qual é o fundamento lógico para fread / fwrite pegar tamanho e contar como argumentos?

96

Tivemos uma discussão aqui no trabalho sobre por que fread e fwrite assumem um tamanho por membro e contam e retornam o número de membros lidos / escritos em vez de apenas pegar um buffer e tamanho. O único uso que podemos sugerir é se você quiser ler / gravar um conjunto de estruturas que não são divisíveis uniformemente pelo alinhamento da plataforma e, portanto, foram preenchidas, mas que não podem ser tão comuns a ponto de justificar essa escolha no design.

De FREAD (3) :

A função fread () lê nmemb elementos de dados, cada um com tamanho em bytes, do fluxo apontado por stream, armazenando-os no local fornecido por ptr.

A função fwrite () grava nmemb elementos de dados, cada tamanho bytes de comprimento, para o fluxo apontado por stream, obtendo-os da localização fornecida por ptr.

fread () e fwrite () retornam o número de itens lidos ou gravados com sucesso (ou seja, não o número de caracteres). Se ocorrer um erro ou se o fim do arquivo for atingido, o valor de retorno será uma contagem curta de itens (ou zero).

David Holm
fonte
10
ei, esta é uma boa pergunta. Eu sempre me perguntei sobre isso
Johannes Schaub - litb

Respostas:

22

É baseado em como o fread é implementado.

A Especificação Única do UNIX diz

Para cada objeto, chamadas de tamanho devem obrigatoriamente ser feitas para a função fgetc () e os resultados armazenados, na ordem de leitura, em um array de caracteres sem sinal sobrepondo exatamente o objeto.

fgetc também tem esta nota:

Uma vez que fgetc () opera em bytes, a leitura de um caractere que consiste em vários bytes (ou "um caractere multibyte") pode exigir várias chamadas para fgetc ().

Claro, isso é anterior a codificações sofisticadas de caracteres de bytes variáveis ​​como UTF-8.

O SUS observa que isso é, na verdade, retirado dos documentos ISO C.

Powerlord
fonte
72

A diferença em fread (buf, 1000, 1, stream) e fread (buf, 1, 1000, stream) é que, no primeiro caso, você obtém apenas um pedaço de 1000 bytes ou nuthin, se o arquivo for menor e no segundo caso, você obtém tudo no arquivo com menos de e até 1000 bytes.

Peter Miehle
fonte
4
Embora seja verdade, isso conta apenas uma pequena parte da história. Seria melhor contrastar algo lendo, digamos, uma matriz de valores int ou uma matriz de estruturas.
Jonathan Leffler
3
Isso seria uma ótima resposta se a justificativa fosse completada.
Matt Joiner
13

Isso é pura especulação, entretanto, no passado (alguns ainda estão por aí), muitos sistemas de arquivos não eram simples fluxos de bytes em um disco rígido.

Muitos sistemas de arquivos eram baseados em registros, portanto, para satisfazer tais sistemas de arquivos de maneira eficiente, você terá que especificar o número de itens ("registros"), permitindo que fwrite / fread opere no armazenamento como registros, não apenas fluxos de bytes.

nos
fonte
1
Estou feliz que alguém trouxe isso à tona. Eu fiz muito trabalho com especificações de sistema de arquivos e FTP e registros / páginas e outros conceitos de bloqueio são muito firmemente suportados, embora ninguém use mais essas partes das especificações.
Matt Joiner
9

Aqui, deixe-me corrigir essas funções:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

Quanto a uma justificativa para os parâmetros para fread()/fwrite() , perdi minha cópia de K&R há muito tempo, então só posso adivinhar. Acho que uma resposta provável é que Kernighan e Ritchie podem ter simplesmente pensado que a execução de E / S binárias seria feita de maneira mais natural em matrizes de objetos. Além disso, eles podem ter pensado que o bloco de E / S seria mais rápido / fácil de implementar ou qualquer outra coisa em algumas arquiteturas.

Mesmo que o padrão C especifique que fread()e fwrite()seja implementado em termos de fgetc()e fputc(), lembre-se de que o padrão passou a existir muito depois de C ser definido por K&R e que as coisas especificadas no padrão podem não estar nas idéias originais dos designers. É até possível que as coisas ditas em "The C Programming Language" da K&R não sejam as mesmas de quando a linguagem estava sendo projetada pela primeira vez.

Finalmente, aqui está o que PJ Plauger tem a dizer sobre fread()em "The Standard C Library":

Se o size (segundo) argumento for maior que um, você não pode determinar se a função também lê size - 1caracteres adicionais além do que relata. Como regra, é melhor chamar a função como, em fread(buf, 1, size * n, stream);vez de fread(buf, size, n, stream);

Basicamente, ele está dizendo que fread()a interface está quebrada. Pois fwrite()ele observa que, "Erros de escrita são geralmente raros, então esta não é uma grande lacuna" - uma afirmação com a qual eu não concordaria.

Michael Burr
fonte
17
Na verdade, geralmente gosto de fazer isso de outra maneira: fread(buf, size*n, 1, stream);se leituras incompletas são uma condição de erro, é mais simples organizar para freadsimplesmente retornar 0 ou 1 em vez do número de bytes lidos. Em seguida, você pode fazer coisas como em if (!fread(...))vez de ter que comparar o resultado com o número de bytes solicitados (o que requer código C extra e código de máquina extra).
R .. GitHub PARAR DE AJUDAR O ICE
1
@R .. Apenas certifique-se de verificar o tamanho * count! = 0 além de! Fread (...). Se size * count == 0, você está obtendo um valor de retorno zero em uma leitura bem - sucedida (de zero bytes), feof () e ferror () não serão definidos e errno será algo sem sentido como ENOENT ou pior , algo enganoso (e possivelmente quebrando criticamente) como EAGAIN - muito confuso, especialmente porque basicamente nenhuma documentação grita que isso te pegue.
Pegasus Epsilon
3

Provavelmente isso remonta à maneira como a E / S do arquivo foi implementada. (naquela época) Pode ter sido mais rápido gravar / ler arquivos em blocos do que gravar tudo de uma vez.

dolch
fonte
Na verdade não. A especificação C para fwrite indica que ele faz chamadas repetidas para fputc: opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord
1

Ter argumentos separados para tamanho e contagem pode ser vantajoso em uma implementação que pode evitar a leitura de quaisquer registros parciais. Se alguém fosse usar leituras de byte único de algo como um tubo, mesmo se estivesse usando dados de formato fixo, seria necessário permitir a possibilidade de um registro ser dividido em duas leituras. Se pudesse, em vez disso, solicitar uma leitura sem bloqueio de até 40 registros de 10 bytes cada quando houver 293 bytes disponíveis, e fazer com que o sistema retornasse 290 bytes (29 registros inteiros), deixando 3 bytes prontos para a próxima leitura, ser muito mais conveniente.

Não sei até que ponto as implementações de fread podem lidar com essa semântica, mas certamente poderiam ser úteis em implementações que poderiam prometer suportá-las.

supergato
fonte
@PegasusEpsilon: Se, por exemplo, um programa o faz fread(buffer, 10000, 2, stdin)e o usuário digita newline-ctrl-D depois de digitar 18.000 bytes, seria bom se a função pudesse retornar os primeiros 10.000 bytes, deixando os 8.000 restantes pendentes para futuras solicitações de leitura menores, mas existem alguma implementação onde isso aconteceria? Onde os 8.000 bytes seriam armazenados durante as solicitações futuras?
supercat
Tendo acabado de testá-lo, descobri que fread () não opera no que eu consideraria a maneira mais conveniente a esse respeito, mas colocar bytes de volta no buffer de leitura após determinar uma leitura curta é provavelmente um pouco mais do que deveríamos esperar de funções de biblioteca padrão de qualquer maneira. fread () irá ler registros parciais e colocá-los no buffer, mas o valor de retorno irá especificar quantos registros completos foram lidos e não diz nada (o que é bastante irritante para mim) sobre quaisquer leituras curtas feitas de stdin.
Pegasus Epsilon
... continuação ... O melhor que você pode fazer é preencher seu buffer de leitura com nulos antes de fread, e verificar o registro depois de onde fread () diz que terminou para quaisquer bytes não nulos. Particularmente, não ajuda quando seus registros podem conter nulos, mas se você for usar sizemaior que 1, bem ... Para o registro, também pode haver ioctls ou outro absurdo que você pode aplicar ao fluxo para torná-lo me comportar de maneira diferente, não me aprofundei muito.
Pegasus Epsilon
Além disso, apaguei meu comentário anterior devido à imprecisão. Ah bem.
Pegasus Epsilon
@PegasusEpsilon: C é usado em muitas plataformas, que acomodam diferentes comportamentos. A noção de que os programadores deveriam esperar usar os mesmos recursos e garantias em todas as implementações ignora o que tinha sido o melhor recurso do C: que seu design permitiria aos programadores usar recursos e garantias nas plataformas onde estavam disponíveis. Alguns tipos de streams podem suportar facilmente pushbacks de tamanho arbitrário e ter o freadtrabalho descrito em tais streams seria útil se houvesse alguma maneira de identificar streams que funcionem dessa maneira.
supercat
0

Acho que é porque C não tem sobrecarga de função. Se houvesse algum, o tamanho seria redundante. Mas em C você não pode determinar o tamanho de um elemento do array, você tem que especificar um.

Considere isto:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

Se fwrite aceitasse o número de bytes, você poderia escrever o seguinte:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

Mas é simplesmente ineficiente. Você terá sizeof (int) vezes mais chamadas de sistema.

Outro ponto que deve ser levado em consideração é que normalmente você não quer que uma parte de um elemento de array seja gravada em um arquivo. Você quer o inteiro inteiro ou nada. fwrite retorna uma série de elementos escritos com sucesso. Então, se você descobrir que apenas 2 bytes baixos de um elemento são escritos, o que você faria?

Em alguns sistemas (devido ao alinhamento), você não pode acessar um byte de um inteiro sem criar uma cópia e deslocar.

Vanuan
fonte