Perché usare bzero su memset?


156

In una lezione di Programmazione di sistemi ho preso questo semestre precedente, abbiamo dovuto implementare un client / server di base in C. All'inizializzazione delle strutture, come sock_addr_in, o char buffer (che usavamo per inviare i dati avanti e indietro tra client e server) il professore ci ha indicato di utilizzare bzeroe non solo memsetdi inizializzarli. Non ha mai spiegato perché, e sono curioso di sapere se esiste una ragione valida per questo?

Vedo qui: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown che bzeroè più efficiente a causa del fatto che sarà sempre azzeramento della memoria, quindi non lo fa deve fare qualsiasi controllo aggiuntivo che memsetpuò fare. memsetTuttavia, ciò non sembra necessariamente un motivo per non utilizzare assolutamente per azzerare la memoria.

bzeroè considerato obsoleto e inoltre non è una funzione C standard. Secondo il manuale, memsetè preferito rispetto bzeroa questo motivo. Allora perché si vuole utilizzare ancora bzerosopra memset? Solo per i guadagni di efficienza o è qualcosa di più? Allo stesso modo, quali sono i vantaggi di memsetOver bzeroche la rendono di fatto l'opzione preferita per i programmi più recenti?


28
"Perché usare bzero su memset?" - Non farlo. Memset è standard, bzero no.

30
bzero è un BSDism (). memset () è ansi-c. al giorno d'oggi, bzero () sarà probabilmente implementato come una macro. Chiedi al tuo professore di radersi e leggere alcuni libri. l'efficienza è un argomento falso. Un syscall o un cambio di contesto possono facilmente costare decine di migliaia di tick di clock, un passaggio su un buffer viene eseguito alla velocità del bus. Se si desidera ottimizzare i programmi di rete: ridurre al minimo il numero di syscall (leggendo / scrivendo blocchi più grandi)
wildplasser,

7
L'idea che memsetpotrebbe essere leggermente meno efficiente a causa di "un po 'più di controllo in corso" è sicuramente un caso di ottimizzazione prematura: qualunque sia il guadagno che potresti vedere omettendo un'istruzione CPU o due non ne vale la pena quando puoi compromettere la portabilità del tuo codice. bzeroè obsoleto e questo è un motivo sufficiente per non usarlo.
dasblinkenlight,

4
Spesso invece è possibile aggiungere un inizializzatore `= {0}` e non chiamare affatto una funzione. Ciò divenne più facile quando verso la fine del secolo C smise di richiedere una dichiarazione anticipata delle variabili locali. Alcuni articoli di carta veramente vecchi sono ancora bloccati in profondità nel secolo precedente, però.
Salterio,

1
@SSAnne no, ma molto probabilmente origine da un libro consigliato per il corso è stato influenzato da, come detto in una delle risposte qui sotto: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Risposte:


152

Non vedo alcuna ragione per preferire bzerosopra memset.

memsetè una funzione C standard mentre bzeronon è mai stata una funzione C standard. La logica è probabilmente perché puoi ottenere esattamente la stessa funzionalità usando la memsetfunzione.

Ora per quanto riguarda l'efficienza, i compilatori come gccusano implementazioni integrate per le memsetquali passano a un'implementazione particolare quando 0viene rilevata una costante . Lo stesso glibcvale per i builtin disabilitati.


Grazie. Questo ha senso. Ero abbastanza sicuro che memsetdovesse sempre essere usato in questo caso, ma ero confuso sul perché non lo stessimo usando. Grazie per aver chiarito e riaffermato i miei pensieri.
PseudoPsyche,

1
Ho avuto molti problemi con bzeroimplementazioni rotte . Sugli array non allineati veniva utilizzato per superare la lunghezza fornita e azzerare un po 'più i byte. Non ho mai avuto un problema del genere dopo il passaggio a memset.
Rustyx,

Non dimenticare memset_squale dovrebbe essere usato se vuoi assicurarti che il compilatore non ottimizzi in modo silenzioso una chiamata per "cancellare" la memoria per uno scopo di sicurezza (come cancellare una regione di memoria che conteneva un sensibile informazione come una password in chiaro).
Christopher Schultz,

69

Immagino che tu abbia usato (o che il tuo insegnante sia stato influenzato da) UNIX Network Programming di W. Richard Stevens. Usa bzerospesso invece di memset, anche nell'edizione più aggiornata. Il libro è così popolare, penso che sia diventato un linguaggio nella programmazione di rete ed è per questo che lo vedi ancora usato.

Vorrei attenermi memsetsemplicemente perché bzeroè deprecato e riduce la portabilità. Dubito che vedresti dei veri guadagni dall'usare l'uno sull'altro.


4
Avresti ragione. Non abbiamo richiesto libri di testo per questo corso, ma ho appena controllato di nuovo il programma e UNIX Network Programming è effettivamente elencato come risorsa opzionale. Grazie.
PseudoPsyche,

9
In realtà è peggio di così. È stato deprecato in POSIX.1-2001 e rimosso in POSIX.1-2008.
paxdiablo,

9
Citando la pagina 8 della terza edizione della Programmazione di rete UNIX di W. Richard Stevens - In effetti, l'autore di TCPv3 ha commesso l'errore di scambiare il secondo e il terzo argomento in memset in 10 occorrenze della prima stampa. Il compilatore CA non è in grado di rilevare questo errore perché entrambe le occorrenze sono uguali ... è stato un errore e potrebbe essere evitato utilizzando bzero, poiché lo scambio dei due argomenti con bzero verrà sempre catturato dal compilatore C se vengono utilizzati i prototipi di funzione. Tuttavia, come sottolineato da paxdiablo, bzero è deprecato.
Aaron Newton,

@AaronNewton, dovresti aggiungerlo alla risposta di Michael poiché conferma ciò che ha detto.
Synetech,

52

L'unico vantaggio che penso bzero()abbia rispetto memset()all'impostazione della memoria su zero è che c'è una possibilità ridotta di errore.

Più di una volta mi sono imbattuto in un bug che sembrava:

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

Il compilatore non si lamenterà (anche se forse alzare alcuni livelli di avviso su alcuni compilatori) e l'effetto sarà che la memoria non viene cancellata. Perché questo non distrugge l'oggetto - lo lascia solo e solo - c'è una buona possibilità che il bug non si manifesti in qualcosa di ovvio.

Il fatto che bzero()non sia standard è un irritante minore. (FWIW, non sarei sorpreso se la maggior parte delle chiamate di funzione nei miei programmi non sono standard; in realtà scrivere tali funzioni è un po 'il mio lavoro).

In un commento a un'altra risposta qui, Aaron Newton ha citato quanto segue da Unix Network Programming, Volume 1, 3rd Edition di Stevens, et al., Sezione 1.2 (enfasi aggiunta):

bzeronon è una funzione ANSI C. È derivato dal primo codice di rete Berkely. Tuttavia, lo usiamo in tutto il testo, anziché nella memsetfunzione ANSI C , perché bzeroè più facile da ricordare (con solo due argomenti) che memset(con tre argomenti). Quasi tutti i fornitori che supportano l'API socket forniscono anche bzero, e in caso contrario, forniamo una definizione macro nella nostra unp.hintestazione.

In effetti, l'autore di TCPv3 [TCP / IP Illustrated, Volume 3 - Stevens 1996] ha commesso l'errore di scambiare il secondo e il terzo argomento memsetin 10 occorrenze nella prima stampa . Il compilatore CA non può rilevare questo errore perché entrambi gli argomenti sono dello stesso tipo. (In realtà, il secondo argomento è un inte il terzo argomento è size_t, che in genere è un unsigned int, ma i valori specificati, 0 e 16, rispettivamente, sono ancora accettabili per l'altro tipo di argomento.) La chiamata a ha memsetfunzionato ancora, perché solo un alcune delle funzioni del socket richiedono effettivamente che gli 8 byte finali di una struttura dell'indirizzo del socket Internet siano impostati su 0. Tuttavia, si è trattato di un errore e si può evitare usando bzero, perché scambiando i due argomenti con bzerosaranno sempre catturati dal compilatore C se vengono utilizzati i prototipi di funzione.

Credo anche che la stragrande maggioranza delle chiamate verso memset()sia a zero memoria, quindi perché non utilizzare un'API su misura per quel caso d'uso?

Un possibile svantaggio bzero()è che i compilatori potrebbero essere più propensi a ottimizzare memcpy()perché è standard e quindi potrebbero essere scritti per riconoscerlo. Tuttavia, tieni presente che il codice corretto è ancora meglio del codice errato che è stato ottimizzato. Nella maggior parte dei casi, l'utilizzo bzero()non causerà un impatto notevole sulle prestazioni del programma e bzero()può essere una funzione macro o incorporata che si espande memcpy().


Sì, suppongo che questo potrebbe essere un ragionamento quando si lavora in un ambiente di classe come questo, in modo da renderlo potenzialmente meno confuso per gli studenti. Non penso che sia stato così per il mio professore. Era un grande insegnante di RTFM. Se avevi una domanda a cui il manuale poteva rispondere, avrebbe tirato su le pagine man sul proiettore in classe e ti avrebbe mostrato. Si preoccupava molto di radicare nella mente di tutti che il manuale è lì per essere letto e risponde alla maggior parte delle tue domande. Sono grato per questo, al contrario di altri professori.
PseudoPsyche,

5
Penso che questo sia un argomento che può essere fatto anche al di fuori della classe: ho visto questo errore nel codice di produzione. Mi sembra un facile errore da fare. Immagino anche che la stragrande maggioranza delle memset()chiamate debba semplicemente azzerare un blocco di memoria, per cui penso sia un altro argomento bzero(). Cosa significa comunque la "b" bzero()?
Michael Burr,

7
+1. Ciò memsetviola un ordinamento di parametri comune di "buffer, buffer_size" lo rende particolarmente soggetto a errori IMO.
jamesdlin,

In Pascal lo evitano chiamandolo "fillchar" e ci vuole un carattere. La maggior parte dei compilatori C / C ++ lo raccolgono. Il che mi fa chiedere perché i compilatori non dicono "stai passando un puntatore a 32/64 bit dove è previsto un byte" e ti danno un calcio fermo negli errori del compilatore.
Móż

1
@Gewure secondo e terzo argomento sono nell'ordine sbagliato; la chiamata di funzione citata non fa esattamente nulla
Ichthyo

4

Volevo menzionare qualcosa sull'argomento bzero vs. memset. Installa ltrace e confronta ciò che fa sotto il cofano. Su Linux con libc6 (2.19-0ubuntu6.6), le chiamate effettuate sono esattamente le stesse (tramite 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)

Mi è stato detto che a meno che non stia lavorando nelle viscere profonde di libc o in qualsiasi numero di interfaccia kernel / syscall, non devo preoccuparmi di loro. Tutto ciò di cui dovrei preoccuparmi è che la chiamata soddisfi il requisito di azzerare il buffer. Altri hanno menzionato quale è preferibile rispetto all'altro, quindi mi fermo qui.


Ciò accade perché alcune versioni di GCC emettono codice per memset(ptr, 0, n)quando lo visualizzano bzero(ptr, n)e non possono convertirlo in codice incorporato.
zwol,

@zwol In realtà è una macro.
SS Anne,

1
@SSAnne gcc 9.3 sul mio computer esegue questa trasformazione da sola, senza alcun aiuto dalle macro nelle intestazioni di sistema. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }produce una chiamata a memset. (Includi stddef.hper size_tsenza nient'altro che possa interferire.)
zwol

4

Probabilmente non dovresti usare bzero, non è in realtà lo standard C, era una cosa POSIX.

E nota che la parola "era" - è stata deprecata in POSIX.1-2001 e rimossa in POSIX.1-2008 in riferimento al memset, quindi stai meglio usando la funzione C standard.


Cosa intendi con standard C? Vuoi dire che non si trova nella libreria C standard?
Koray Tugay,

@Koray, lo standard C indica lo standard ISO e, sì, bzeronon ne fa parte.
paxdiablo,

No, voglio dire, non so cosa intendi per standard. Standard ISO significa libreria C standard? Viene fornito con la lingua? La libreria minima che sappiamo che sarà lì?
Koray Tugay,

2
@Koray, ISO è l'organizzazione degli standard che è responsabile dello standard C, quello attuale è C11 e quelli precedenti C99 e C89. Stabiliscono le regole che un'implementazione deve seguire per essere considerata C. Quindi sì, se lo standard dice che un'implementazione deve fornire memset, sarà lì per te. Altrimenti, non è C.
paxdiablo,

2

Per la funzione memset, il secondo argomento è un inte il terzo argomento è size_t,

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

che in genere è un unsigned int, ma se i valori simili, rispettivamente 0 and 16per il secondo e il terzo argomento, sono inseriti in un ordine errato come 16 e 0, allora una tale chiamata a memset può ancora funzionare, ma non farà nulla. Perché il numero di byte da inizializzare è specificato come 0.

void bzero(void *s, size_t n)

Un tale errore può essere evitato usando bzero, poiché lo scambio dei due argomenti in bzero verrà sempre intercettato dal compilatore C se vengono utilizzati i prototipi di funzione.


1
Un tale errore può anche essere evitato con memset se pensi semplicemente alla chiamata come "imposta questa memoria su questo valore per questa dimensione", o se hai un IDE che ti dà il prototipo o anche solo se sai cosa sei fare :-)
paxdiablo

D'accordo, ma questa funzione è stata creata nel momento in cui tali IDE intelligenti non erano disponibili per il supporto.
havish

2

In breve: memset richiedono quindi più operazioni di assemblaggio bzero.

Questa è la fonte: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown


Sì, questa è una cosa che ho menzionato nel PO. In realtà ho anche collegato a quella pagina esatta. Si scopre che non sembra fare molta differenza a causa di alcune ottimizzazioni del compilatore. Per maggiori dettagli vedi la risposta accettata da ouah.
PseudoPsyche,

6
Questo dimostra solo che l'implementazione di immondizia di memset è lenta. Su MacOS X e alcuni altri sistemi, memset utilizza il codice che viene impostato all'avvio in base al processore in uso, fa pieno uso dei registri vettoriali e, per grandi dimensioni, utilizza le istruzioni di prefetch in modi intelligenti per ottenere l'ultimo bit di velocità.
gnasher729,

un minor numero di istruzioni non significa un'esecuzione più rapida. In effetti, le ottimizzazioni spesso aumentano la dimensione binaria e il numero di istruzioni a causa di srotolamento di loop, allineamento di funzioni, allineamento di loop ... Guarda qualsiasi codice ottimizzato decente e vedrai che spesso ha molte più istruzioni di implementazioni di merda
phuclv

2

Fallo nel modo che preferisci. :-)

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

Nota che:

  1. L'originale bzeronon restituisce nulla, memsetrestituisce il puntatore vuoto ( d). Questo può essere risolto aggiungendo il typecast a void nella definizione.
  2. #ifndef bzeronon ti impedisce di nascondere la funzione originale anche se esiste. Verifica l'esistenza di una macro. Ciò può causare molta confusione.
  3. È impossibile creare un puntatore a una macro. Quando si utilizza bzerotramite i puntatori a funzione, questo non funzionerà.

1
Qual è il problema con questo, @Leeor? Antipatia generale per le macro? O non ti piace il fatto che questa macro possa essere confusa con la funzione (e forse anche la nasconde)?
Palec,

1
@Palec, quest'ultimo. Nascondere una ridefinizione come macro può creare tanta confusione. Un altro programmatore che usa questo codice pensa di usare una cosa ed è inconsapevolmente costretto a usare l'altra. Questa è una bomba a orologeria.
Leeor,

1
Dopo averci pensato un altro, sono d'accordo che questa è davvero una cattiva soluzione. Tra le altre cose ho trovato un motivo tecnico: quando si utilizza bzerotramite i puntatori a funzione, questo non funzionerà.
Palec,

Avresti dovuto chiamare la tua macro in modo diverso bzero. Questa è un'atrocità.
Dan Bechard,

-2

memset accetta 3 parametri, bzero ne prende 2 in memoria vincolata che un parametro aggiuntivo richiederebbe 4 byte in più e il più delle volte verrà usato per impostare tutto su 0

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.