2015-12-31 11 views
8

Desidero memorizzare diversi tipi di dati nella stessa memoria allocata per ridurre il tempo di esecuzione allocando la memoria una sola volta. Ho scoperto che è effettivamente possibile creare un array di variabili uint8_t e creare un nuovo puntatore uint16_t sullo stesso indirizzo di memoria e quindi posso leggere i valori in entrambi i modi.Va bene memorizzare diversi tipi di dati nella stessa memoria allocata in C?

Ciò mi consente di creare un puntatore diciamo al centro della memoria allocata e di memorizzare i valori nella seconda metà in un diverso tipo di dati.

Va bene così? So che devo occuparmi dei limiti della memoria, ma questo è un cattivo stile?

Ecco il mio codice:

#include <stdio.h> 
#include <inttypes.h> 
#include <stdint.h> 
#include <stdlib.h> 

int main(void){ 
    uint8_t *array; 
    uint16_t *array2; 
    array = calloc(6, 1); 
    array[0] = 257; 

    printf("array[0]= %" PRIu8 "\n", array[0]); 
    printf("array[1]= %" PRIu8 "\n", array[1]); 
    printf("Adresse von array[0] = %p\n", &array[0]); 
    array2 = &array[0]; 
    printf("Adresse auf die array2 zeigt = %p\n", array2); 

    array2[0] = 257; 
    printf("array2[0]= %" PRIu16 "\n", array2[0]); 
    printf("array2[1]= %" PRIu16 "\n", array2[1]); 
    printf("array[0]= %" PRIu8 "\n", array[0]); 
    printf("array[1]= %" PRIu8 "\n", array[1]); 

    getchar(); 
    return 0; 
} 
+8

leggi su unione –

+0

Ma in realtà non si memorizzano dada di tipi di dati diversi nella stessa memoria allocata. – ameyCU

+4

Effettuare questa operazione manualmente è una cattiva pratica poiché i tipi di dati potrebbero avere requisiti di allineamento della memoria diversi. Usa un 'unione'. – kfx

risposta

10

Utilizzare un union per creare una variabile che a volte negozi di un tipo, a volte un'altra.

union { 
    uint8_t u8; 
    uint16_t u16; 
} *array_u; 
size_t nmemb = 6; 

array_u = calloc(nmemb, sizeof *array_u); 
assert(array_u); 

printf("array_u[0].u8 = %" PRIu8 "\n", array_u[0].u8); 

array_u[0].u16 = 1234; 
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16); 
... 

Questo non usa tutto lo spazio con un solo uint8_t u8 ogni unione. Il sotto utilizza 2 uint8_t.

union { 
    uint8_t u8[2]; 
    uint16_t u16; 
} *array_u; 

printf("array_u[0].u8[0] = %" PRIu8 "\n", array_u[0].u8[0]); 

array_u[0].u16 = 1234; 
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16); 

OTOH Se il codice ha bisogno di sovrapporre un intero array di lunghezza fissa

union { 
    uint8_t u8[12]; 
    uint16_t u16[6]; 
} *array_u; 

array_u = calloc(1, sizeof *array_u); 
assert(array_u); 

printf("array_u->u8[0] = %" PRIu8 "\n", array_u->u8[0]); 

array_u->u16[0] = 1234; 
printf("array_u->u16[0] = %" PRIu16 "\n", array_u->u16[0]); 
... 
+0

Ottima risposta! Molte grazie! C'è qualche differenza tra 'array_u-> u16 [0]' e 'array_u [0] .u16 [0]'? – TimFinnegan

+2

@TimFinnegan Nessuna differenza funzionale tra 'array_u-> u16 [0]' e 'array_u [0] .u16 [0]'. Usa il modulo che meglio esprime il significato del tuo codice. – chux

4

È che va bene a farlo?

Lo standard dice:

Un puntatore a un tipo di oggetto può essere convertito in un puntatore a un tipo di oggetto diverso. Se il puntatore risultante non è allineato correttamente per il tipo di riferimento, il comportamento non è definito. Altrimenti, una volta convertito nuovamente, il risultato deve essere uguale al puntatore originale.

(C2011, 6.3.2.3/7)

Nota che non hanno nemmeno bisogno di uso il puntatore convertito per la produzione di un comportamento indefinito - il comportamento della conversione in sé non è definito.

Detto, il codice esempio mostra un caso particolare: il puntatore restituito da una chiamata riuscita al calloc() (o malloc() o realloc()) è garantito da allineare in modo appropriato per un oggetto di qualsiasi tipo, per cui la conversione puntatore specifico eseguire (array2 = &array[0]) dovrebbe essere sempre ok. Dovrebbe, tuttavia, richiedere un cast. Il compilatore dovrebbe avvertirti di questo.

Se si pianifica invece di convertire i puntatori in posizioni arbitrarie all'interno del blocco assegnato, non è possibile fare affidamento sull'allineamento corretto.

Si noti inoltre che con questo schema si rischia di danneggiare le prestazioni anziché migliorarlo. In particolare, alcuni processori hanno un carico molto migliore e memorizzano le prestazioni per i dati allineati in modo appropriato, anche se possono gestire altri allineamenti. Si paga il costo di allocazione della memoria una volta per blocco - si paga qualsiasi costo di accesso disallineato ad ogni accesso.

So che devo occuparmi dei limiti della memoria ma è questo stile negativo?

Sì, molto. Stai sacrificando la chiarezza del codice per un guadagno di performance incerto e ipotetico. È probabile che i maggiori costi di sviluppo e manutenzione aumentino il guadagno in termini di prestazioni, specialmente dal momento che offuscano anche l'intento del compilatore, rendendo così più difficile generare codice macchina efficiente.

Inoltre, è probabilmente troppo presto per iniziare questo tipo di micro-ottimizzazione. Fai in modo che il tuo programma funzioni correttamente. Se non è abbastanza veloce, prova per trovare i colli di bottiglia e concentrarsi sul miglioramento di tali parti. È più probabile che tu migliori le prestazioni scegliendo algoritmi migliori; piccoli trucchi come quelli che proponi valgono raramente.

+1

Buona analisi dei problemi. – chux

Problemi correlati