Array o Malloc?

11

Sto usando il seguente codice nella mia applicazione, e sta funzionando bene. Ma mi chiedo se è meglio farlo con malloc o lasciarlo così com'è?

function (int len)
{
char result [len] = some chars;
send result over network
}
    
posta Dev Bag 10.04.2012 - 23:16
fonte

9 risposte

26

La differenza principale è che i VLA (array di lunghezza variabile) non forniscono alcun meccanismo per rilevare i problemi di allocazione.

Se dichiari

char result[len];

e len supera la quantità di spazio disponibile nello stack, il comportamento del tuo programma non è definito. Non esiste un meccanismo linguistico per determinare in anticipo se l'assegnazione avrà successo o per determinare dopo il fatto se ha avuto successo.

D'altra parte, se scrivi:

char *result = malloc(len);
if (result == NULL) {
    /* allocation failed, abort or take corrective action */
}

quindi puoi gestire i fallimenti con garbo, o almeno garantire che il tuo programma non tenti di continuare a eseguire dopo un errore.

(Beh, soprattutto. Su sistemi Linux, malloc() può allocare un blocco di spazio indirizzo anche se non è disponibile alcuna memoria corrispondente, i tentativi successivi di utilizzare quello spazio possono richiamare OOM Killer . Ma il controllo dell'errore di malloc() è comunque una buona pratica.)

Un altro problema, su molti sistemi, è che c'è più spazio (forse un lotto più spazio) disponibile per malloc() rispetto a oggetti automatici come VLA.

E come la risposta di Philip già menzionata, i VLA sono stati aggiunti in C99 (in particolare Microsoft non li supporta).

E i VLA sono stati resi facoltativi in C11. Probabilmente la maggior parte dei compilatori C11 li supporterà, ma non puoi contare su di essi.

    
risposta data 11.04.2012 - 03:35
fonte
14

Gli array automatici a lunghezza variabile sono stati introdotti in C in C99.

A meno che tu non abbia dubbi sulla retrocompatibilità con gli standard precedenti, va bene.

In generale, se funziona, non toccarlo. Non ottimizzare prima del tempo. Non preoccuparti di aggiungere funzionalità speciali o modi intelligenti di fare le cose, perché spesso non lo userai. Mantienilo semplice.

    
risposta data 10.04.2012 - 23:23
fonte
8

Se il compilatore supporta matrici di lunghezza variabile, l'unico pericolo è quello di far traboccare lo stack su alcuni sistemi, quando il len è ridicolmente grande. Se sai con certezza che len non sarà più grande di un certo numero e sai che lo stack non ha overflow anche alla lunghezza massima, lascia il codice così com'è; altrimenti, riscrivilo con malloc e free .

    
risposta data 10.04.2012 - 23:27
fonte
6

Mi piace l'idea che si possa avere un array assegnato in fase di esecuzione senza frammentazione della memoria, puntatori penzolanti, ecc. Tuttavia, altri hanno sottolineato che questa allocazione run-time può fallire silenziosamente. Così ho provato questo usando gcc 4.5.3 in un ambiente bash Cygwin:

#include <stdio.h>
#include <string.h>

void testit (unsigned long len)
{
    char result [len*2];
    char marker[100];

    memset(marker, 0, sizeof(marker));
    printf("result's size: %lu\n", sizeof(result));
    strcpy(result, "this is a test that should overflow if no allocation");
    printf("marker's contents: '%s'\n", marker);
}

int main(int argc, char *argv[])
{
    testit(100);
    testit((unsigned long)-1);  // probably too big
}

L'output era:

$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'

La lunghezza eccessivamente grande passata nella seconda chiamata ha chiaramente causato l'errore (traboccante nel marker []). Ciò non significa che questo tipo di controllo sia infallibile (gli sciocchi possono essere intelligenti!) O che rispetti gli standard di C99, ma potrebbe esserti utile se hai questa preoccupazione.

Come al solito, YMMV.

    
risposta data 11.04.2012 - 21:55
fonte
3

In generale, lo stack è il posto più semplice e migliore dove mettere i tuoi dati.

Eviterei i problemi degli VLA semplicemente assegnando l'array più grande che ci si aspetta.

Tuttavia, ci sono casi in cui l'heap è il migliore e fare casino con malloc vale la pena.

  1. Quando la sua grande ma variabile quantità di dati. Grande dipende dal tuo ambiente > 1K per sistemi incorporati, > 10 MB per un server Enterprise.
  2. Quando vuoi che i dati persistano dopo l'uscita dalla routine, ad es. se restituisci un puntatore ai tuoi dati. Usando
  3. Una combinazione di puntatore statico e malloc () è in genere migliore della definizione di un array statico di grandi dimensioni;
risposta data 11.04.2012 - 03:48
fonte
3

Nella programmazione incorporata, usiamo sempre array statici invece di malloc quando le operazioni malloc e free sono frequenti. A causa della mancanza di gestione della memoria nel sistema embedded, l'allocazione frequente e le operazioni libere causeranno un frammento di memoria. Ma dovremmo utilizzare alcuni metodi complicati come la definizione della dimensione massima dell'array e l'uso di array statici locali.

Se la tua applicazione è in esecuzione in Linux o Windows, non è questione di usare array o malloc. Il punto chiave si trova dove usi la struttura della data e la logica del codice.

    
risposta data 16.05.2012 - 22:33
fonte
1

Qualcosa che nessuno ha ancora menzionato è che l'opzione dell'array di lunghezza variabile sarà probabilmente molto più veloce di malloc / free dato che l'allocazione di un VLA è solo un caso di aggiustamento del puntatore dello stack (almeno in GCC).

Quindi, se questa funzione è quella che viene chiamata frequentemente (che ovviamente si determina mediante il profiling), il VLA è una buona opzione di ottimizzazione.

    
risposta data 11.04.2012 - 17:11
fonte
1

Questa è una soluzione C molto comune che utilizzo per il problema che potrebbe essere di aiuto. A differenza dei VLA, non presenta alcun rischio pratico di overflow dello stack in casi patologici.

/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
    /// Stores raw bytes for fast access.
    char fast_mem[512];

    /// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
    /// dynamically allocated memory address.
    void* data;
};

/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
    // Utilize the stack if the memory fits, otherwise malloc.
    mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
    return mem->data;
}

/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
    // Free the memory if it was allocated dynamically with 'malloc'.
    if (mem->data != mem->fast_mem)
        free(mem->data);
    mem->data = 0;
}

Per usarlo nel tuo caso:

struct FastMem fm;

// 'result' will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);

// send result over network.
...

// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);

Cosa succede nel caso precedente è usare lo stack se la stringa si adatta a 512 byte o meno. Altrimenti usa un'allocazione dell'heap. Questo può essere utile se, per esempio, il 99% delle volte, la stringa si adatta a 512 byte o meno. Ad ogni modo, diciamo che c'è un pazzo caso esotico che potresti dover occasionalmente gestire dove la stringa è di 32 kilobyte dove l'utente si è addormentato sulla sua tastiera o qualcosa del genere. Ciò consente di gestire entrambe le situazioni senza problemi.

La versione effettiva che uso in produzione ha anche una propria versione di realloc e calloc e così via, nonché strutture di dati C ++ conformi allo standard costruite sullo stesso concetto, ma ho estratto il minimo necessario per illustrare il concept.

Ha l'avvertenza che è pericoloso copiarlo e non devi restituire i puntatori assegnati attraverso di esso (potrebbero finire per essere invalidati quando l'istanza FastMem viene distrutta). È pensato per essere utilizzato per casi semplici nell'ambito di una funzione locale in cui si sarebbe tentati di utilizzare sempre lo stack / VLA in caso contrario alcuni casi rari potrebbero causare overflow buffer / stack. Non è un allocatore generico e non dovrebbe essere usato come tale.

In realtà l'ho creato anni fa in risposta a una situazione in una base di codice legacy che utilizzava C89 che un ex team pensava non sarebbe mai accaduto dove un utente è riuscito a nominare un oggetto con un nome di oltre 2047 caratteri (forse si è addormentato sulla sua tastiera). I miei colleghi hanno effettivamente cercato di aumentare la dimensione degli array allocati in vari luoghi a 16.384 in risposta, a quel punto ho pensato che sarebbe diventato ridicolo e si sarebbe appena scambiato un maggior rischio di overflow dello stack in cambio di un minore rischio di overflow del buffer. Ciò ha fornito una soluzione che è stata molto facile da collegare per risolvere quei casi aggiungendo semplicemente un paio di righe di codice. Ciò ha permesso di gestire il caso comune in modo molto efficiente e di utilizzare ancora lo stack senza quei pazzi casi rari che richiedevano il crash del software. Tuttavia, l'ho trovato utile da allora anche dopo il C99 dal momento che gli VLA non possono ancora proteggerci contro gli overflow dello stack. Questo può, ma raggruppare ancora dallo stack per richieste di allocazione ridotte.

    
risposta data 07.12.2017 - 06:40
fonte
1

Lo stack di chiamate è sempre limitato. Nei sistemi operativi mainstream come Linux o Windows il limite è di uno o pochi megabyte (e potresti trovare il modo di cambiarlo). Con alcune applicazioni multi-thread, potrebbe essere inferiore (perché i thread potrebbero essere creati con uno stack più piccolo). Sui sistemi embedded, potrebbe essere piccolo come pochi kilobyte. Una buona regola empirica è quella di evitare telegrammi più grandi di pochi kilobyte.

Quindi usare un VLA ha senso solo se sei sicuro che il tuo len sia abbastanza piccolo (a la maggior parte poche decine di migliaia). Altrimenti hai un overflow dello stack e questo è un caso di comportamento non definito , una situazione molto spaventosa .

Tuttavia, l'uso manuale allocazione di memoria dinamica C (ad esempio calloc o malloc & free ) ha anche i suoi svantaggi:

  • può fallire e dovresti sempre testare il fallimento (ad esempio calloc o malloc restituendo NULL ).

  • è più lento: un'allocazione VLA di successo richiede pochi nanosecondi, un riuscito malloc potrebbe richiedere diversi microsecondi (nei casi buoni, solo una frazione di microsecondo) o anche di più (in casi patologici che coinvolgono < a href="https://en.wikipedia.org/wiki/Thrashing_(computer_science)"> thrashing , molto altro).

  • è molto più difficile da codificare: puoi free solo quando sei sicuro che la zona puntata non è più utilizzata. Nel tuo caso potresti chiamare sia calloc che free nella stessa routine.

Se sai che la maggior parte delle volte il tuo result (un nome molto povero, tu mai dovrebbe restituire l'indirizzo di un variabile automatica VLA; quindi sto usando buf invece di result di seguito) è piccolo potresti avere un caso speciale, ad esempio

char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf) 
  free(buf);

Tuttavia, il codice precedente è meno leggibile ed è probabilmente l'ottimizzazione prematura. È tuttavia più robusto di una soluzione VLA pura.

PS. Alcuni sistemi (ad esempio alcune distribuzioni Linux sono abilitate per impostazione predefinita) hanno memoria overcommitment (che rende malloc dando qualche puntatore anche se non c'è abbastanza memoria). Questa è una caratteristica che non mi piace e di solito disabilita sulle mie macchine Linux.

    
risposta data 07.12.2017 - 07:01
fonte

Leggi altre domande sui tag