Por que usar bzero sobre memset?

156

Em uma aula de Programação de Sistemas que fiz neste semestre anterior, tivemos que implementar um cliente / servidor básico em C. Ao inicializar estruturas, como sock_addr_inou buffers de char (que usamos para enviar dados entre cliente e servidor), o professor nos instruiu a usar apenas bzeroe não memsetinicializá-los. Ele nunca explicou o porquê e estou curioso para saber se existe uma razão válida para isso.

Vejo aqui: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown que bzeroé mais eficiente devido ao fato de que apenas zerará a memória, por isso não precisa fazer qualquer verificação adicional que memsetpossa fazer. Isso ainda não parece necessariamente uma razão para absolutamente não usar memsetpara zerar a memória.

bzeroé considerado obsoleto e, além disso, não é uma função C padrão. De acordo com o manual, memseté preferível a bzeroesse motivo. Então, por que você iria querer ainda usam bzeromais memset? Apenas pelos ganhos de eficiência, ou é algo mais? Da mesma forma, quais são os benefícios do memsetexcesso bzeroque o tornam a opção preferida de fato para programas mais recentes?

PseudoPsique
fonte
28
"Por que usar bzero sobre memset?" - não. Memset é padrão, bzero não é.
30
O bzero é um BSDism (). memset () é ansi-c. Atualmente, o bzero () provavelmente será implementado como uma macro. Peça ao seu professor que se barbeie e leia alguns livros. eficiência é um argumento falso. Um syscall ou switch de contexto pode facilmente custar dezenas de milhares de clock, uma passagem por um buffer é executada na velocidade do barramento. Se você quiser rede-programas optimize: minimizar o número de syscalls (através da leitura / escrita pedaços maiores)
wildplasser
7
A idéia que memsetpode ser um pouco menos eficiente por causa de "um pouco mais de verificação" é definitivamente um caso de otimização prematura: quaisquer que sejam os ganhos que você pode ver ao omitir uma ou duas instruções da CPU, não valem a pena quando você pode comprometer a portabilidade do seu código. bzeroé obsoleto, e isso é motivo suficiente para não usá-lo.
dasblinkenlight
4
Freqüentemente, você pode adicionar um inicializador `= {0}` e não chamar uma função. Isso ficou mais fácil quando, por volta da virada do século C, parou de exigir uma declaração antecipada das variáveis ​​locais. Alguns papéis realmente antigos ainda estão presos no século anterior.
MSalters
1
@SSAnne não, mas provavelmente se originou de um livro recomendado para o curso pelo qual ele foi influenciado, como mencionado em uma das respostas abaixo: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Respostas:

152

Não vejo qualquer razão para preferir bzeromais memset.

memseté uma função C padrão, embora bzeronunca tenha sido uma função padrão C. A lógica é provavelmente porque você pode obter exatamente a mesma funcionalidade usando a memsetfunção

Agora, com relação à eficiência, os compiladores gccusam implementações embutidas paramemset quais alternam para uma implementação específica quando uma constante 0é detectada. O mesmo para glibcquando os recursos internos estão desativados.

ouah
fonte
Obrigado. Isso faz sentido. Eu tinha certeza de que memsetsempre deveria ser usado nesse caso, mas estava confuso sobre o motivo de não usá-lo. Obrigado por esclarecer e reafirmar meus pensamentos.
PseudoPsyche 13/06
1
Eu tive muitos problemas com bzeroimplementações quebradas . Em matrizes não alinhadas, costumava ultrapassar o comprimento fornecido e zerar um pouco mais bytes. Nunca tive esse problema depois de mudar para memset.
rustyx
Não se esqueça de memset_squal deve ser usado se você quiser garantir que o compilador não otimize silenciosamente uma chamada para "limpar" a memória para algum propósito relacionado à segurança (como apagar uma região da memória que continha uma informação sensível). informações, como uma senha de texto não criptografado).
Christopher Schultz
69

Acho que você usou (ou seu professor foi influenciado por) UNIX Network Programming por W. Richard Stevens. Ele usa com bzerofrequência, em vez de memset, mesmo na edição mais atualizada. O livro é tão popular que acho que se tornou um idioma na programação de redes, e é por isso que você ainda o vê sendo usado.

Eu ficaria com memsetsimplesmente porque bzeroé preterido e reduz a portabilidade. Duvido que você tenha algum ganho real ao usar um sobre o outro.

Austin
fonte
4
Você estaria correto. Não exigimos livros didáticos para este curso, mas acabei de verificar o programa novamente e a Programação em Rede UNIX está realmente listada como um recurso opcional. Obrigado.
PseudoPsyche 13/06
9
Na verdade, é pior que isso. Foi preterido no POSIX.1-2001 e removido no POSIX.1-2008.
precisa
9
Citando a página 8 da terceira edição da UNIX Network Programming por W. Richard Stevens - De fato, o autor do TCPv3 cometeu o erro de trocar o segundo e o terceiro argumentos pelo memset em 10 ocorrências da primeira impressão. O compilador CA não pode capturar esse erro porque ambas as ocorrências são iguais ... foi um erro e pode ser evitado com o bzero, porque a troca dos dois argumentos pelo bzero sempre será capturada pelo compilador C se forem utilizados protótipos de função. No entanto, como paxdiablo apontou, o bzero está obsoleto.
Aaron Newton
@ AaronNewton, você deve adicionar isso à resposta de Michael, pois confirma o que ele disse.
Synetech
52

A única vantagem que acho bzero()que sobrou memset()para configurar a memória como zero é que há uma chance reduzida de cometer um erro.

Mais de uma vez me deparei com um bug que parecia:

memset(someobject, size_of_object, 0);    // clear object

O compilador não irá reclamar (embora talvez aumente alguns níveis de aviso em alguns compiladores) e o efeito será que a memória não é limpa. Como isso não descarta o objeto - apenas o deixa em branco - há uma chance decente de que o bug não se manifeste em nada óbvio.

O fato de bzero()não ser padrão é um irritante menor. (FWIW, eu não ficaria surpreso se a maioria das chamadas de função nos meus programas não fosse padrão; de fato, escrever essas funções é o meu trabalho).

Em um comentário a outra resposta aqui, Aaron Newton citou o seguinte em Unix Network Programming, Volume 1, 3rd Edition por Stevens, et al., Seção 1.2 (ênfase adicionada):

bzeronão é uma função ANSI C. É derivado do código de rede Berkely antigo. No entanto, nós o usamos em todo o texto, em vez da memsetfunção ANSI C , porque bzeroé mais fácil de lembrar (com apenas dois argumentos) do que memset(com três argumentos). Quase todos os fornecedores que oferecem suporte à API de soquetes também fornecem bzeroe, se não, fornecemos uma definição de macro em nosso unp.hcabeçalho.

De fato, o autor do TCPv3 [TCP / IP Illustrated, Volume 3 - Stevens 1996] cometeu o erro de trocar o segundo e o terceiro argumentos memsetem 10 ocorrências na primeira impressão . O compilador CA não pode capturar esse erro porque os dois argumentos são do mesmo tipo. (Na verdade, o segundo argumento é um inte o terceiro argumento é size_t, que geralmente é um unsigned int, mas os valores especificados, 0 e 16, respectivamente, ainda são aceitáveis ​​para o outro tipo de argumento.) A chamada para memsetainda funcionou, porque apenas um algumas das funções de soquete realmente exigem que os 8 bytes finais de uma estrutura de endereço de soquete da Internet sejam definidos como 0. No entanto, foi um erro e que poderia ser evitado usando bzero, porque a troca dos dois argumentos bzerosempre será capturada pelo compilador C se protótipos de função forem usados.

Também acredito que a grande maioria das chamadas para memset()a memória é zero, então por que não usar uma API que é personalizada para esse caso de uso?

Uma possível desvantagem bzero()é que é mais provável que os compiladores otimizem memcpy()porque é padrão e, portanto, eles podem ser escritos para reconhecê-lo. No entanto, lembre-se de que o código correto ainda é melhor que o código incorreto que foi otimizado. Na maioria dos casos, o uso bzero()não causará um impacto perceptível no desempenho do programa e bzero()pode ser uma função macro ou embutida que se expande para memcpy().

Michael Burr
fonte
Sim, suponho que esse possa ser um raciocínio ao trabalhar em uma sala de aula como essa, para tornar potencialmente menos confuso para os alunos. Eu não acho que esse foi o caso do meu professor, no entanto. Ele era um professor muito grande de RTFM. Se você tivesse uma pergunta que pudesse ser respondida pelo manual, ele abriria as páginas de manual do projetor na sala de aula e mostraria a você. Ele estava muito interessado em enraizar na mente de todos que o manual está aí para ser lido e responde à maioria das suas perguntas. Sou grato por isso, ao contrário de alguns outros professores.
PseudoPsyche
5
Eu acho que esse é um argumento que pode ser feito mesmo fora da sala de aula - eu vi esse bug no código de produção. Parece-me um erro fácil de cometer. Eu também acho que a grande maioria das memset()chamadas é simplesmente zerar um bloco de memória, o que eu acho que é outro argumento bzero(). O que significa o 'b' bzero()de qualquer maneira?
Michael Burr
7
+1. Isso memsetviola uma ordem comum de parâmetros de "buffer, buffer_size", tornando-o IMO particularmente propenso a erros.
Jamesdlin
Em Pascal, eles evitam isso chamando-o de "fillchar" e é necessário um caractere. A maioria dos compiladores C / C ++ escolheria esse. O que me faz pensar por que os compiladores não dizem "você está passando um ponteiro de 32/64 bits onde um byte é esperado" e o chutam firmemente nos erros do compilador.
30313
1
@ O segundo e o terceiro argumentos da Gewure estão na ordem errada; a chamada de função citado faz exatamente nada
Ichthyo
4

Queria mencionar algo sobre o argumento bzero vs. memset. Instale o ltrace e compare o que ele faz sob o capô. No Linux com libc6 (2.19-0ubuntu6.6), as chamadas feitas são exatamente as mesmas (via ltrace ./test123):

long m[] = {0}; // generates a call to memset(0x7fffefa28238, '\0', 8)
int* p;
bzero(&p, 4);   // generates a call to memset(0x7fffefa28230, '\0', 4)

Foi-me dito que, a menos que eu esteja trabalhando nas entranhas profundas da libc ou em qualquer número de interfaces kernel / syscall, não preciso me preocupar com elas. Tudo o que me preocupa é que a chamada atenda ao requisito de zerar o buffer. Outros mencionaram sobre qual é preferível em relação ao outro, então vou parar por aqui.

gumchew
fonte
Isso acontece porque algumas versões do GCC emitem código para memset(ptr, 0, n)quando veem bzero(ptr, n)e não podem convertê-lo em código embutido.
Zwol 23/05/19
@zwol Na verdade, é uma macro.
SS Anne
1
O @SSAnne gcc 9.3 no meu computador faz essa transformação sozinho, sem nenhuma ajuda de macros nos cabeçalhos do sistema. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }produz uma chamada para memset. (Inclua stddef.hpara size_tsem qualquer outra coisa que possa interferir.)
zwol 15/04
4

Você provavelmente não deveria usar bzero, não é realmente C padrão, era uma coisa do POSIX.

E observe que a palavra "was" - foi preterida no POSIX.1-2001 e removida no POSIX.1-2008 em deferência ao memset, para que você esteja melhor usando a função C padrão.

paxdiablo
fonte
O que você quer dizer com padrão C? Você quer dizer que não foi encontrado na biblioteca C padrão?
precisa saber é o seguinte
@ Koray, o padrão C significa o padrão ISO e, sim, bzeronão faz parte disso.
paxdiablo
Não, quero dizer, não sei o que você quer dizer com nenhum padrão. O padrão ISO significa a biblioteca C padrão? Isso vem com o idioma? A biblioteca mínima que sabemos que estará lá?
Koray Tugay
2
@Koray, ISO é a organização de padrões responsável pelo padrão C, sendo o atual C11 e os anteriores C99 e C89. Eles estabelecem as regras que uma implementação deve seguir para ser considerada C. Então, sim, se o padrão diz que uma implementação deve fornecer um memset, ela estará lá para você. Caso contrário, ele não é C. #
384
2

Para a função memset, o segundo argumento é um int e o terceiro argumento é size_t,

void *memset(void *s, int c, size_t n);

que normalmente é um unsigned int , mas se os valores como, 0 and 16para o segundo e terceiro argumento, respectivamente, forem inseridos na ordem errada como 16 e 0, essa chamada para memset ainda poderá funcionar, mas não fará nada. Porque o número de bytes a inicializar são especificados como 0.

void bzero(void *s, size_t n)

Esse erro pode ser evitado usando o bzero, porque a troca dos dois argumentos pelo bzero sempre será capturada pelo compilador C se forem utilizados protótipos de função.

havish
fonte
1
Esse erro também pode ser evitado com o memset se você simplesmente considerar a chamada como "defina esta memória com esse valor para esse tamanho" ou se você tiver um IDE que fornece o protótipo ou mesmo se você apenas sabe o que está fazendo. fazendo :-)
paxdiablo
Concordo, mas essa função foi criada no momento em que esses IDEs inteligentes não estavam disponíveis para o suporte.
havish 25/07/15
2

Em resumo: memset exija mais operações de montagem bzero.

Esta é a fonte: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

Tal Bar
fonte
Sim, isso é uma coisa que eu mencionei no OP. Na verdade, eu até vinculei a essa página exata. Acontece que isso realmente não faz muita diferença devido a algumas otimizações do compilador. Para mais detalhes, consulte a resposta aceita por ouah.
PseudoPsyche
6
Isso mostra apenas que uma implementação de lixo do memset é lenta. No MacOS X e em alguns outros sistemas, o memset usa código configurado no momento da inicialização, dependendo do processador que você está usando, faz uso total de registros vetoriais e, para tamanhos grandes, usa instruções de pré-busca de maneiras inteligentes para obter o último bit. de velocidade.
precisa saber é o seguinte
menos instruções não significa execução mais rápida. De fato, as otimizações geralmente aumentam o tamanho binário e o número de instruções devido ao desenrolar do loop, alinhamento de funções, alinhamento de loop ... Olhe para qualquer código otimizado decente e você verá que muitas vezes tem muito mais instruções do que implementações de merda
phuclv
2

Faça como quiser. :-)

#ifndef bzero
#define bzero(d,n) memset((d),0,(n))
#endif

Observe que:

  1. O original bzeronão retorna nada, memsetretorna o ponteiro nulo ( d). Isso pode ser corrigido adicionando o typecast a anular na definição.
  2. #ifndef bzeronão impede que você esconda a função original, mesmo que ela exista. Ele testa a existência de uma macro. Isso pode causar muita confusão.
  3. É impossível criar um ponteiro de função para uma macro. Ao usar bzeroponteiros de função, isso não funcionará.
Bruce
fonte
1
Qual é o problema com isso, @Leeor? Antipatia geral por macros? Ou você não gosta do fato de que essa macro pode ser confundida com a função (e possivelmente até a oculta)?
Palec 29/04
1
@Palec, o último. Ocultar uma redefinição como macro pode levar a muita confusão. Outro programador que usa esse código pensa que está usando uma coisa e, sem saber, é forçado a usar a outra. Isso é uma bomba-relógio.
Leeor
1
Depois de pensar novamente, concordo que essa é realmente uma solução ruim. Entre outras coisas, encontrei um motivo técnico: ao usar bzeroponteiros de função, isso não funciona.
Palec 30/04/2015
Você realmente deveria ter chamado sua macro de algo diferente bzero. Isso é uma atrocidade.
Dan Bechard
-2

O memset usa 3 parâmetros, o bzero ocupa 2 na memória restrita, que o parâmetro extra levaria mais 4 bytes e na maioria das vezes será usado para definir tudo como 0

Skynight
fonte