2013-03-27 13 views
7

Sto esaminando il libro Pratica di programmazione C di O'Reilly e ho letto il libro K & R sul linguaggio di programmazione C e ho davvero difficoltà ad afferrare il concetto dietro i sindacati.Qual è il punto dietro i sindacati in C?

Prendono la dimensione del tipo di dati più grande che li compone ... e l'ultima assegnata sovrascrive il resto ... ma perché non usare solo/libera la memoria, se necessario?

Il libro menziona che è usato nella comunicazione, dove è necessario impostare bandiere della stessa dimensione; e su un sito web su Google, che può eliminare frammenti di memoria di dimensioni dispari ... ma è di qualche utilità in uno spazio di memoria moderno, non incorporato?

C'è qualcosa di furbo che puoi fare con i registri della CPU? È semplicemente una presa da un'era precedente di programmazione? Oppure, come il famigerato goto, ha ancora un uso potente (possibilmente in spazi ristretti di memoria) che merita di essere tenuto in considerazione?

+1

Date un'occhiata a [Tipo-punning] (http://en.wikipedia.org/wiki/Type_punning). – Mysticial

+0

Può essere utile in situazioni in cui è necessario convertire una struct alla sua valori binari (per esempio un char []), senza utilizzare hack puntatore. –

+0

possibile duplicato di [C/C++: quando qualcuno userebbe un sindacato? È fondamentalmente un residuo dei soli giorni C?] (Http: // StackOverflow.it/questions/4788965/cc-when-would-anyone-use-a-union-is-it-fondament-a-remnant-from-the-c-only) –

risposta

5

Bene, hai quasi risposto alla tua domanda: Memoria. Indietro nei giorni la memoria era piuttosto bassa, e anche il salvataggio di pochi kbyte è stato utile.

Ma anche oggi ci sono scenari in cui i sindacati sarebbero utili. Ad esempio, se desideri implementare una sorta di tipo di dati variant. Il modo migliore per farlo è usare un sindacato.

Non sembra molto, ma supponiamo di voler usare una variabile memorizzando una stringa di 4 caratteri (come un ID) o un numero di 4 byte (che potrebbe essere un po 'di hash o semplicemente un numero) .

Se si utilizza un classico struct, questo sarebbe lungo 8 byte (almeno, se sei sfortunato, si stanno riempiendo anche i byte). Usando un union è solo 4 byte. Quindi stai risparmiando il 50% di memoria, che non è molto per un'istanza, ma immagina di averne un milione.

Mentre è possibile ottenere risultati simili mediante il cast o la creazione di sottoclassi, l'unione è ancora il modo più semplice per farlo.

1

Un uso di unioni consiste nel fatto che due variabili occupano lo stesso spazio e una seconda variabile nella struttura decide quale tipo di dati si desidera leggerlo.

ad es. potresti avere un booleano "isDouble" e un union "doubleOrLong" che ha sia un doppio che un lungo. Se isDouble == true interpreta il sindacato come un doppio altrimenti lo interpreta come un lungo.

Un altro uso di unioni è l'accesso ai tipi di dati in diverse rappresentazioni. Ad esempio, se sai come viene messo in memoria un doppio, puoi mettere un doppio in un sindacato, accedervi come un tipo di dati diverso come un lungo, accedere direttamente ai suoi bit, alla sua mantissa, al suo segno, al suo esponente, qualunque sia e fare qualche manipolazione diretta con esso.

Al giorno d'oggi questo non è davvero necessario poiché la memoria è così economica, ma nei sistemi embedded ha i suoi usi.

+1

Anche in macchine moderne con tonnellate di gigabyte di RAM esso non farà del male a risparmiare memoria in base al tuo scenario, ad es quando si salva qualcosa su disco, quando si invia qualcosa su una rete o semplicemente in base al numero di set di dati. – Mario

+1

@ Mario concordato. L'argomento secondo cui la memoria è a buon mercato e non hai bisogno di preoccuparti delle dimensioni della struttura mi da sempre un odore di design pigro. Supponi di voler memorizzare cento milioni di record in memoria e che i record contengano 32 diverse bandiere booleane. Usi 32 booleani o un intero XOR a 32 bit. Ignorando il riempimento, si tratta di una differenza di circa 3 GB. – Anthony

0

L'API di Windows utilizza molto le unioni. LARGE_INTEGER è un esempio di tale utilizzo. Fondamentalmente, se il compilatore supporta interi a 64 bit, usa il membro QuadPart; altrimenti, impostare manualmente DWORD basso e DWORD alto.

0

Non è davvero una presa in giro, come il linguaggio C è stato creato nel 1972, quando la memoria era una vera preoccupazione.

Si potrebbe argomentare che in uno spazio moderno non incorporato, si potrebbe non voler usare C come linguaggio di programmazione per cominciare. Se hai scelto C come scelta linguistica per l'implementazione, stai cercando di sfruttare i vantaggi di C: è efficiente, vicino al metallo, il che si traduce in binari stretti e veloci.

Come tale, quando si sceglie di utilizzare C, si sarebbe ancora voglia di approfittare dei suoi vantaggi, che comprende l'efficienza della memoria-spazio. Al che, l'Unione funziona molto bene; consentendo di avere un certo grado di sicurezza del tipo, mentre si applica la più piccola impronta di memoria disponibile.

+0

In realtà, la mia ragione per tornare a rivedere C, quindi C++, quindi Templating in C++, poi CLR, quindi C++/CLI è perché sto cercando di creare un aggiornamento dell'indice a 64 bit (o superiore) per .Net List/Dictionary classi e classi associate/spazi dei nomi. Ho un prurito da graffiare, quindi ho intenzione di "sistemare" quello che considero rotto qui. – user978122

0

Un posto in cui l'ho visto usato è l'attuazione Doom 3/4 IDTech Fast Inverse Square Root.

Per chi non conosce questo algoritmo, essenzialmente richiede il trattamento di un numero in virgola mobile come un intero. Il vecchio Quake (e precedenti) versione del codice fa questo dal seguente:

float y = 2.0f; 

// treat the bits of y as an integer 
long i = * (long *) &y; 

// do some stuff with i 

// treat the bits of i as a float 
y = * (float *) &i; 

original source on GitHub

Questo codice prende l'indirizzo di un numero in virgola mobile y, l'inserisce in un puntatore a una lunga (cioè, un numero intero a 32 bit in giorni Quake), e lo definisce in i. Poi fa qualcosa di incredibilmente bizzarro, e il contrario.

Ci sono due svantaggi di farlo in questo modo. Uno è che l'indirizzo-di involuta, fuso, processo dereferenziare forza il valore di y da leggere dalla memoria, invece che da un registro , e idem al ritorno. Sui computer dell'era di Quake, tuttavia, i registri in virgola mobile e integer erano completamente separati, quindi dovevi praticamente spingere alla memoria e tornare indietro per gestire questa restrizione.

Il secondo è che, almeno in C++, fare questo tipo di casting è profondamente disapprovato, anche quando si fa ciò che equivale al voodoo come fa questa funzione. Sono sicuro che ci sono argomenti più interessanti, però io non sono sicuro di quello che sono :)

Così, in Doom 3, id inclusi i seguenti bit nella loro nuova applicazione (che utilizza un diverso insieme di bit giocherellando, ma un'idea simile):

union _flint { 
     dword     i; 
     float     f; 
}; 

... 
union _flint seed; 
seed.i = /* look up some tables to get this */; 
double r = seed.f; // <- access the bits of seed.i as a floating point number 

original source on GitHub

Teoricamente, su una macchina SSE2, questo si può accedere attraverso un unico registro; Non sono sicuro nella pratica se un compilatore lo farebbe. A mio parere, è ancora un po 'un codice più pulito rispetto ai giochi di casting nella precedente versione di Quake.


- ignorando "compilatore sufficientemente avanzati" argomenti