API migliore e più sicura per una funzione che riempie un buffer con dati a lunghezza variabile

3

Ho una funzione che riceve un buffer e restituisce alcuni dati nel buffer. I dati possono essere più piccoli della capacità del buffer.

Qual è l'API migliore e più sicura per questo?

  • int fn(void *buffer, size_t buffer_len) : la dimensione dei dati scritti in buffer viene restituita dalla funzione. Lato negativo: il valore di ritorno deve anche avere un modo per indicare che si è verificato un errore ( indicatore di errore in-banda ).
  • errno_t fn(void *buffer, size_t *buffer_len) : in questo caso, buffer_len funziona sia come input (la capacità del buffer) che come output (la dimensione dei dati). La funzione può restituire un codice di errore. Penso che sia OK, ma un po 'imbarazzante.
  • errno_t fn(void *buffer, size_t *data_len, size_t buffer_len) : come il precedente, ma con input / output separati in due argomenti. Restituisce anche il codice di errore, ma è anche scomodo a causa di troppi argomenti.

(Qualsiasi altra opzione?)

    
posta Conrado 18.04.2014 - 00:37
fonte

4 risposte

2

Penso che la migliore API per questo caso sia quella di creare una specifica del buffer e gestirla, ad esempio:

struct sbuf {
  void *data;
  size_t size, pos, length;
};

// void if malloc() errors do abort(). int otherwise, to report error
void sbuf_alloc(sbuf *sb, size_t minsize);
void sbuf_free(sbuf *sb);

int /* or errno_t, whatever */ fn(sbuf *sb);  

Con tale API, eviterete 1) lunghi ed ingombranti elenchi di argomenti, 2) possibilità di scambiare la lunghezza per la dimensione e viceversa, e otterrete invece la comprensione di un aspetto simile agli oggetti dei vostri buffer.

    
risposta data 21.04.2014 - 08:31
fonte
1

Una domanda che abbiamo meditato frequentemente.

  1. Guarda come vengono gestite situazioni simili altrove nella tua API. La coerenza è una priorità molto alta e se ci sono principi consolidati, attenersi a questi.
  2. La lingua conta. È difficile / impossibile restituire valori nei parametri in alcune lingue (Java).
  3. Considera l'utilizzo di una struct o di una raccolta invece di un puntatore raw. L'array di byte in Java ne contiene la lunghezza.
  4. I parametri I / O sono orribili con cui codificare. Evitalo!
  5. Un valore di ritorno non dovrebbe essere sia un indicatore di successo che una lunghezza, ma se infrange le regole questo è il meno grave.

In generale, la mia preferenza in C / C ++ è di restituire bool success / failure e di fornire una funzione secondaria (GetLastError ()) per ottenere il vero codice di errore.

Nel caso di Read (solo), la mia preferenza è di restituire la lunghezza letta come valore di ritorno della funzione, con un valore speciale per l'errore (-1). Non è il più bello, ma conveniente.

Se i dati che stai leggendo sono noti per essere Ascii, quindi restituisci una stringa con terminazione null per fornire la lunghezza e boolé true / false per la funzione return.

[Purtroppo questa è solo una questione di opinione, il che rende questa una domanda a rischio di essere chiusa.]

    
risposta data 18.04.2014 - 11:16
fonte
1

Supporterei il punto di david di mantenere la coerenza, e poiché questo sembra essere C (e non C ++ ), la funzionalità richiesta è molto simile al file POSIX letto:
buffer di input invece di un file.
buffer di output per l'utente, rimane lo stesso.
numero di byte letti o lunghezza, rimane uguale.

Quindi, penso che attenersi a POSIX sia una buona cosa, quindi la prima opzione sarebbe migliore, con un ritorno di -1 per errore (i valori negativi per più errori sono possibili ma appaiono maldestri):
int fn(void *buffer, size_t buffer_len)

    
risposta data 21.04.2014 - 08:40
fonte
0

Quale approccio è il migliore dipende da quando nel processo si trova il codice riempire il buffer diventerà consapevole di quanto deve essere grande il buffer e se sia possibile eseguire parzialmente l'operazione, restituire al chiamante, quindi continuare l'operazione in un secondo momento (dopo il chiamante o ha creato un buffer più grande o ha fatto tutto ciò che avrebbe mai dovuto essere fatto con il contenuto presente del buffer, permettendo così lo spazio riutilizzata). Un buon approccio generale sarebbe quello di passare un vuoto ** insieme a a funzione che ha un vuoto ** e una dimensione richiesta, si comporterà in a moda approssimativamente equivalente a:

void *resize(void **handle, size_t size)
{
  void *new_mem = realloc(*handle, size);
  if (!new_mem) return 0;
  *handle = new_mem;
  return new_mem;
}

ma potrebbe utilizzare meccanismi di allocazione diversi da realloc per acquisire memoria. L'utilizzo di tale approccio consentirebbe la funzione che sta riempiendo il buffer per espanderlo mentre procede. Un altro approccio è definire un astratto "stato" struttura, tale che il modello di utilizzo sarebbe:

GRABBER_STATE *state = start_reading_data(...);
do {
  int n=get_items(state, buffer, buff_size));
  if (!n) break;
  process_n_items(buffer, n);
} while(1);
done_reading_data(state);

Qui non importa se il buffer è abbastanza grande da contenere tutto. Ciò che è importante è che se ci sono elementi che non vengono gestiti prima volta attraverso il ciclo, possono essere gestiti su passate successive.

    
risposta data 21.04.2016 - 21:50
fonte

Leggi altre domande sui tag