Tag: Enum, Union, Struct - STB1019/SkullOfSummer GitHub Wiki

Fonte : WG14/N1256 Committee Draft — Septermber 7, 2007 ISO/IEC 9899:TC3 (ISO 31−11, ISO/IEC 646, ISO/IEC 2382−1:1993, ISO 4217, ISO 8601, ISO/IEC 10646, IEC 60559:1989)Link

Tutte le dichiarazioni di struct, union e tutti i tipi numerabili (come enum) che sono dotati dello stesso scope ed usano lo stesso tag, indicano la dichiarazione del medesimo tipo. La dichiarazione del tipo di dato è incompleta sino alla chiusura della graffa, che conclude la lista di attributi.

Ciascuna di queste dichiarazioni, non dovrebbe contenere al suo interno un'ulteriore dichiarazione di ulteriori Tag. Ma è possibile utilizzare come attributi, puntatori a tali strutture.

Vincoli

  1. Un tipo specifico deve essere dichiarato al più una sola volta.
  2. Se due dichiarazioni che utilizzano lo stesso tag per dichiarare lo stesso tipo, allora necessariamente devono entrambe utilizzare una delle seguenti keyword struct, union o enum.

Enum

enum permette di dichiarare enumeration, ovvero un insieme finito di int semanticamente simile. A ciascun int appartenente all'enum viene assegnato un valore intero specifico. Tale valore può essere assegnato esplicitamente, oppure implicitamente associando il valore numerico successivo al precedente enumeratore.

enum nome-enumerations {nome-enumeratore-0,nome-enumeratore-1,...}.

Gli enum sono molto utili quando, per esempio, si deve popolare una variabile che può assumere solo be precisi valori interi. Si supponga per esempio che, in un programma, la variabile season rappresenti una stagione. Invece di implicitamente assegnare:

  • 0 per la primavera;
  • 1 per l'estate;
  • 2 per l'autunno;
  • 3 per l'inverno;

è molto meglio usare un enum:

enum seasons_t { SPRING, SUMMER, FALL, WINTER };

In questo modo, nel codice, non ci saranno magic numbers sparsi ovunque, ma identificatori ben riconoscibili!

Dichiarazione implicita, esplicita e mista

È possibile assegnare dei valori specifici ai singoli enumeratori e, successivamente lasciare che sia il compilatore ad associare ai restanti enumeratori, i valori successivi.

Dichiarazione esplicita del valore degli enumeratori

enum numerations_esplicita {
    a=30,
    b=11,
    c=34,
    d=27,
    e=4
};

int main() {
    printf("%d %d %d",a,b,c);       //30 11 34
}

Dichiarazione implicita del valore degli enumeratori

enum numerations_implicita {
    a,
    b,
    c,
    d,
    e
};

int main() {
    printf("%d %d %d",a,b,c);      //0 1 2
}

Dichiarazione mista

enum numerations_mista {
    a,
    b=14,
    c,
    d=-2,
    e
};

int main() {
    printf("%d %d %d %d %d",a,b,c,d,e);      //0 14 15 -2 -1
}

Leggibilità

Come per i typedef (wiki), l'uso degli enum aumenta notevolmente la leggibilità del codice, ed ancor di più la sua manutenibilità.

Union

Una variabile definita definita come union è sufficiente per contenere il più grande tra i suoi attributi: ogni attirbuto della union condivide la stessa area di memoria; per cui modificare un attributo della union può modificare immediatamente i rimanenti attributi della stessa. In pratica: il puntatore di ogni attributo all'interno della union punterà sempre al primo byte in cui la union è allocata in memoria.

Le union sono ottime per gestire (efficaciemente a livello di memoria) un insieme di attributi in cui uno ed uno solo è valido in un qualunque istante di tempo. Per esempio supponiamo di dover gestire un dato generico letto dalla command line:

enum possible_value_t {
    VAR_INT,
    VAR_LONG,
    VAR_CHAR,
    VAR_STRING
};

union actual_value_t {
    int* int_val;
    long* long_val;
    char* char_val;
    char* string_val;
};

Supponi di aver letto qualcosa dalla commandline avente un particolare tipo (e che tu sappia esattamente il tipo che hai letto): l'enum definisce cosa è stato letto mentre la union contiene un puntatore in cui immagazzinare il valore letto. Il codice potrebbe essere una cosa di questo tipo:

switch (possible_value) {
    case VAR_INT:
        actual_value.int_val = //malloc pointer and save data from strtol
        break;
    case VAR_LONG:
        actual_value.long_val = //malloc pointer and save data from strtol
        break;
    case VAR_CHAR:
        actual_value.char_val = //malloc pointer and save data from *((char*)args[i])
        break;
    case VAR_STRING:
        actual_value.string_val = //malloc pointer and save data from a copy of args[i]
        break;
    default:
        fprintf(stderr, "tipo non valido\n");
        exit(1);
        break;
}

Esempio Ref

union nome-union { dichiarazione variabile a; dichiarazione variabile b; dichiarazione variabile c; };

Esempio

#include <stdio.h>

union values {
    int int_val;
    float float_val;
    char char_val;
};

int main() {
    union values union_variable;
    union_variable.int_val = 512; //first byte is 0!
    printf("the size of the union is the size of the field having the longest size required: %ld\n", sizeof(union_variable));
    printf("the memory for each field is the same. int value is %d but char value is \"%c\" since the first byte of the int value is 0.\n", union_variable.int_val, union_variable.char_val);

    printf("On my computer int are long %ld and I have big endian (most significant byte is on the right)\n", sizeof(int));
    printf("making sure the first byte of union_variable.int_val is 0b01000001...\n");
    //int_val and char_val both share the first byte in memory
    union_variable.int_val = 0b01000001;
   
    printf("the memory for each field is the same. int value is %d but char value is \"%c\" since the first byte of the int value is 65 (hence A).\n", union_variable.int_val, union_variable.char_val);
}

Struct

struct permette di definire una tipologia di dato complesso, composto da attributi, ovvero ulteriori tipologie di dato; quest'ultimi possono essere eterogenei tra di loro. È possibile realizzare una struttura autoreferenziata.

struct nome-tipo-struttura { dichiarazione-variabile-1 dichiarazione-variabile-2 dichiarazione-variabile-3 ...};

Ciascuna variabile definita come struct occupa una dimensione pari al totale delle dimensioni di ciascun attributo della struct. In caso di attributi composti da array di dimensione non definita, la dimensione totale non varierà in base a questa dato che la dimensione di una struct è sempre definita in fase di compilazione.

#include<stdio.h>
    
struct a0 {
    int a;
};

struct a1 {
    int a;
    int b[];
};

struct a2 {
    int a;
    int b[100];
};

int main() {
    printf("sizeof a0: %ld\n", sizeof(a0));
    printf("sizeof a1: %ld\n", sizeof(a1));
    printf("sizeof a2: %ld\n", sizeof(a2));
}

Variabile Struttura

Dichiarazione di una struttura anagrafica :

struct anagrafica{
    char nome[100];
    char cognome[100];
    int eta;
};

Dichiarazione di una variabile struttura di tipo anagrafica :

struct anagrafica soggetto;

Accesso agli attributi di soggetto:

Per accedere agli attributi della variabile di struttura si utilizza l'operatore '.' :

soggetto.eta=45;
strcpy(soggetto.cognome,"Rossi");
strcpy(soggetto.nome,"Mario");

L'operatore -> è una shortcut per accedere ai campi ad un puntatore a strutture (-> è ammissibile anche per una union):

struct a {
    int val1;
    int* val2;
}

int main() {
    struct a* struct_var;
    ...
    //these 2 instructions do the same job
    (*struct_var).val2
    struct_var->val2;
}

In caso di inizializzazione della variabile, è possibile definire gli attributi con la forma compatta, rispettando l'ordine in cui questi sono dichiarati:

anagrafica soggetto_a={"Mario","Rossi",45};
anagrafica soggetto_b={"Rossi","Mario",45};

In questo esempio, soggetto_b ha l'attributo cognome pari a "Mario", mentre il soggetto_a per il medesimo attributo possiede il valore "Rossi".

Ciascun attributo è allocato in una propria cella di memoria.

Struttura autoreferenziata

È possibile definire ulteriori struct all'interno di una di queste, purchè non si tratti della medesima. Il solo modo per poter autorefenziare una 'struct' è attraverso l'uso dei puntatori.

struct persona{
       char CF[100];
       ...
       struct persona *next,*prev;
};

Una struttura autoreferenziata è consigliata per la gestione di liste (o grafi) complesse di dati compositi; ciascun nodo è legato al predecessore ed al successore (ad eccezion fatta per il primo e l'ultimo).

Per accedere all'attributo CF della struttura puntata da *next ( o *prev), è sufficiente scrivere

struct persona soggetto,*subject;
...
strcpy(soggetto.next->CF,"CGMNME90A01H501T"); //soggetto è di tipo "struct persona"
strcpy(subject->next->CF,"CGMNME90A01H501T"); //subject è di tipo "struct persona*"
...

Puntatore di Struttura

Valgono le medesime regole (ed accorgimenti) per una variabile di una struttura, ad eccezione dell'operatore che non è più '.', ma bensì '->'. Consideriamo la seguente definizione di una variabile ed un puntatore per la medesima struttura (di cui la dichiarazione contempla l'esistenza dei medesimi attributi).

struct tipo_struttura variabile, *pointer;

L'espressione pointer->attributo_1 è analoga ad variabile.attributo_1, ed entrambi permettono l'accesso a attributo_1.

Struttura con variabili di dimensione non definita

È possibile inserire come attributo vettori la cui dimensione non sia definita:

struct s { int n; double d[]; };

Prima di poter assegnare un qualsiasi valore all'attributo d, è necessario definire la dimensione come segue

int m = 5; //qualsiasi valore
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

Di seguito abbiamo diverse dichiarazioni (alcuni valide ed altre no) usando la medesima dichiarazione di struttura riportata poco sopra:

struct s t1 = { 0 }; // valida
struct s t2 = { 1, { 4.2 }}; // errata 
t1.n = 4; // valida
t1.d[0] = 4.2; // potrebbe dare errore

Fonte : WG14/N1256 Committee Draft — Septermber 7, 2007 ISO/IEC 9899:TC3 (ISO 31−11, ISO/IEC 646, ISO/IEC 2382−1:1993, ISO 4217, ISO 8601, ISO/IEC 10646, IEC 60559:1989)Link

Curiosità

Stramberie con le union

Le union possono essere usate per creare comportamenti molto complessi e poci debuggabili, come ad esempio questo (notare che valgrind intercettsa diversi problemi con questo programma!):

#include <stdio.h>
#include <string.h>
#define WORD_SIZE 256

struct template{
   char send_id[6];
   char dest_id[6];
   char type[3];
   char mess[240];
};

union payload{
    struct template dataset;
    char blob[WORD_SIZE];
};

void printField(char[],int);

int main(){
   union payload tmp;
   strcpy(tmp.dataset.send_id,"123ABCD");
   strcpy(tmp.dataset.dest_id,"456DEF");
   strcpy(tmp.dataset.type,"200");
   strcpy(tmp.dataset.mess,"Messaggio ricevuto");
   printf("\nSTART\n");
   printf("SENDER\t\t %s\nRECEIVER\t %s\nTYPE OF MESSAGE\t %s \nMESSAGE\t\t %s \nBLOB\t\t %s\n",tmp.dataset.send_id,tmp.dataset.dest_id,tmp.dataset.type,tmp.dataset.mess,tmp.blob);
   printf("\nRemember EOF and %%s\n");
   printf("\nSENDER\t\t");
   printField(tmp.dataset.send_id,6);
   printf("\nRECEIVER\t");
   printField(tmp.dataset.dest_id,6);
   printf("\nTYPE OF MESSAGE\t");
   printField(tmp.dataset.type,3);
   printf("\nMESSAGE\t\t");
   printField(tmp.dataset.mess,240);
   printf("\nEND\n");
   return 0;
}

void printField(char elem[],int size){
 char tmp[size];
 for(int i=0;i<size;i++)
    tmp[i]=elem[i];
 printf("%s",tmp);
}

Altre stramberie con le union

Inoltre è possibile generare codice strambo con le union, se uno vuole proprio farsi male!

...
union
{
    int j;
    float f;
    unsigned int i;
} u;

u.f = 3.0f;
for(int i=0;i<5;i++){
    printf("As integer: %d %08x As float: %f %08x\n",u.i, u.i,u.f,u.f);
}
....

output differente

union
{
    int j;
    float f;
    unsigned int i;
} u;

u.j = 3.0f;
for(int i=0;i<5;i++){
    printf("As integer: %d %08x As unsigned integer: %d %08x As float: %f %08x\n",u.j,u.j, u.i, u.i,u.f,u.f);
}

Spiegazione plausibile ref

I'm assuming that when you say "my particular hardware", you are referring to an Intel processor using SSE floating point. But in fact, that architecture has a different rule, according to the Intel® 64 and IA-32 Architectures Software Developer's Manual. Here's a summary of Table 4.7 ("Rules for handling NaNs") from Volume 1 of that documentation, which describes the handling of NaNs in arithmetic operations: (QNaN is a quiet NaN; SNaN is a signalling NaN; I've only included information about two-operand instructions)

  • SNaN and QNaN
    • x87 FPU — QNaN source operand.
    • SSE — First source operand, converted to a QNaN.
  • Two SNaNs
    • x87 FPU — SNaN source operand with the larger significand, converted to a QNaN
    • SSE — First source operand, converted to a QNaN.
  • Two QNaNs
    • x87 FPU — QNaN source operand with the larger significand
    • SSE — First source operand
  • NaN and a floating-point value
    • x87/SSE — NaN source operand, converted to a QNaN.

SSE floating point machine instructions generally have the form op xmm1, xmm2/m32, where the first operand is the destination register and the second operand is either a register or a memory location. The instruction will then do, in effect, xmm1 <- xmm1 (op) xmm2/m32, so the first operand is both the left-hand operand of the operation and the destination. That's the meaningof "first operand" in the above chart. AVX adds three-operand instructions, where the destination might be a different register; it is then the third operand and does not figure in the above chart. The x87 FPU uses a stack-based architecture, where the top of the stack is always one of the operands and the result replaces either the top of the stack or the other operand; in the above chart, it will be noted that the rules do not attempt to decide which operand is "first", relying instead on a simple comparison. Now, suppose we're generating code for an SSE machine, and we have to handle the C statement:

a = b + c;

where none of those variables are in a register. That means we might emit code something like this: (I'm not using real instructions here, but the principle is the same)

LOAD  r1, b  (r1 <- b)
ADD   r1, c  (r1 <- r1 + c)
STORE r1, a  (a  <- r1)

But we could also do this, with (almost) the same result:

LOAD  r1, c  (r1 <- c)
ADD   r1, b  (r1 <- r1 + b)
STORE r1, a  (a  <- r1)`

That will have precisely the same effect, except for additions involving NaNs (and only when using SSE). Since arithmetic involving NaNs is unspecified by the C standard, there is no reason why the compiler should care which of these two options it chooses. In particular, if r1 happened to already have the value c in it, the compiler would probably choose the second option, since it saves a load instruction. (And who is going to complain? We all want the compiler to generate code which runs as quickly as possible, no?) So, in short, the order of the operands of the ADD instruction will vary with the intricate details of how the compiler chooses to optimize the code, and the particular state of the registers at the moment in which the addition operator is being emitted. It is possible that this will be effected by the use of a union, but it is equally or more likely that it has to do with the fact that in your code using the union, the values being added are arguments to the function and therefore are already placed in registers. Indeed, different versions of gcc, and different optimization settings, produce different results for your code. And forcing the compiler to emit x87 FPU instructions produces yet different results, because the hardware operates according to a different logic.

⚠️ **GitHub.com Fallback** ⚠️