2010-11-11 12 views
9

Prima di array di lunghezza variabile sono stati sostenuti, vorrei li allocare dinamicamente in questo modo:Come si impedisce agli array di lunghezza variabile di arrestarsi in modo anomalo quando la memoria non è sufficiente?

int foo(size_t n) 
{ 
    int *arr = malloc(n * sizeof int); 
    if (!arr) return ENOMEM; /* not enough memory */ 
    . 
    . else do stuff with arr[] 
    . 
    free(arr); 
    return 0; 
} 

Con gli array a lunghezza variabile ora posso farlo sembrare più pulita:

int bar(size_t n) 
{ 
    int arr[n]; 
    . 
    . do stuff with arr[] 
    . 
    return 0; 
} 

Ma ora non hanno il controllo di "memoria insufficiente". In effetti, il programma si blocca se n è troppo grande.

Come posso eseguire il pagamento con garbo dalla barra (n) se n è troppo grande?

+0

Il limite può (sistema dipendente, ovviamente) non essere "memoria" ma "dimensione dello stack". E ora la domanda ha più senso per me perché stavo pensando alla tua implementazione "prima" su una scatola dei consumatori ragionevolmente moderna e mi chiedevo * "Cosa sta facendo che ha bisogno di gran parte di un GB?" *. Comunque. Se lo stack ti limita, il "vecchio" modo potrebbe essere migliore. – dmckee

+0

Questo è esattamente il motivo per cui gli VLA non vengono utilizzati più di tanto (un altro è scarso supporto nei compilatori). I VLA C99 semplicemente non sono stabili. – VSG24

risposta

12

La situazione è esattamente immutata da qualsiasi altro variabili locali - una dichiarazione come questa:

int baz(void) 
{ 
    int arr[100000]; 
    . 
    . do stuff with arr[] 
    . 
    return 0; 
} 

ha esattamente lo stesso problema. La "soluzione" è la stessa che è sempre stata: non recidare troppo a fondo e non allocare strutture di dati molto grandi con la durata di archiviazione automatica (continuare a utilizzare malloc() per questi casi). Il valore di "molto grande" dipende fortemente dal tuo ambiente.

In altre parole, non dichiarare int array[n]; a meno che non si sa che n è delimitata a un valore ragionevole, in modo tale che si sarebbe stato felice di dichiarare un array di dimensioni massime come un non-variabile-modificato tipo ordinario, array.

(Sì, questo significa che le matrici di tipo variabilmente modificate non sono così utili come appaiono per la prima volta, dal momento che si guadagna molto poco semplicemente dichiarando l'array alla dimensione massima necessaria).

+0

Questo lo mette in prospettiva, grazie. Potrei decidere su una dimensione massima e iniziare la funzione con 'if (n <= MAX) {int arr [n]; ...} ' – Henry

6

È possibile impedire che si blocchino non utilizzandoli. :)

In tutta serietà, non c'è quasi nessun modo sicuro per usare matrici di lunghezza variabile per rendere la vita più facile a meno che non si abbiano forti limiti sulle dimensioni. D'altra parte, è possibile utilizzare in modo condizionale, in modo simile a questo:

char vla_buf[n < 1000 ? n : 1]; 
char *buf = sizeof vla_buf < n ? malloc(n) : vla_buf; 
if (!buf) goto error; 
/* ... Do stuff with buf ... */ 
if (buf != vla_buf) free(buf); 

Mentre questo appare come il dolore inutile, si può fare una grande differenza di prestazioni, soprattutto nelle applicazioni filettate dove molte chiamate a malloc e free potrebbe causare un conflitto di blocco. (Un vantaggio collaterale notevole di questo trucco è che è possibile supportare i vecchi compilatori senza VLA con la semplice sostituzione [n < 1000 ? n : 1] con 1000, ad esempio con una macro.)

Un altro caso oscuro dove i VLA può essere utile è in algoritmi ricorsivi in ​​cui si conosce il il numero totale di voci di array richieste per tutti i livelli di ricorsione è limitato da n, dove n è sufficientemente piccolo da essere sicuro che non verrà sovraccaricato dallo stack, ma dove potrebbero esserci fino a n livelli di ricorsione e livelli individuali che si consumano a n elementi. Prima di C99, l'unico modo per gestire questo caso senza lo spazio di stack n^2 era utilizzare malloc. Con gli VLA, puoi risolvere il problema interamente in pila.

Tenete a mente, questi casi in cui i VLA sono davvero utili sono piuttosto dannosi. Normalmente VLA è solo un modo per ingannare te stesso che la gestione della memoria è facile, fino a quando non ottieni le vulnerabilità risultanti (triviali da sfruttare) che hai creato.:-)

Edit: Per domanda iniziale migliore indirizzo di OP:

#define MAX_VLA 10000 
int bar(size_t n) 
{ 
    int arr[n <= MAX_VLA ? n : 1]; 
    if (sizeof arr/sizeof *arr < n) return ENOMEM; 
    /* ... */ 
    return 0; 
} 
+0

Nel tuo primo esempio, potresti anche usare' [1000] 'ovunque, dato che hai già stabilito che quel valore non andrà (non dovrebbe) in crash. L'algoritmo ricorsivo sembra essere il vero caso d'uso. – caf

+0

Mi hai dato alcune buone idee, ma penso che il modo più semplice sia semplicemente avviare la funzione con 'if (n <= MAX_VLA) {int arr [n]; ...} '. Grazie. – Henry

+0

Nota che ci sono errori nei tuoi esempi. :-) Dovrebbe essere 'if ((sizeof arr)/(sizeof int) Henry

0

In realtà è proibitivo per verificare la presenza di condizioni di memoria in tutto il mondo. Il modo intraprendente per gestire enormi quantità di dati consiste nel limitare le dimensioni dei dati definendo le dimensioni dell'hard cap sul singolo punto di controllo iniziale e fallire rapidamente e con garbo quando si colpisce il limite.

Quello che ho appena suggerito è semplice e stupido. Ma è ciò che fa sempre ogni prodotto ordinario (non scientifico o speciale). E ciò che normalmente è previsto dal cliente.

Problemi correlati