In this class, you should not attempt to use unions in your code unless you are asked to do so. However, some of my code will have unions, so you'll have to know how to deal with them.
A union is very simple -- it looks just like a struct:
typedef union { int i; float f; double d; } Ifd;However, in a struct, you can use any and all of those fields. In a union, you can only use one at a time. Think if it as an either-or kind of thing. If I declare an Ifd above, then I can use it as an int, a float or a double, but I can't interchange them.
Here is a trivial example, using the above struct (this is in union1.c):
#include <stdio.h> typedef union { int i; float f; double d; } Ifd; main() { Ifd ifd; int i; float f; ifd.i = 1; i = 1; printf("%d %d\n", i, ifd.i); ifd.f = 5.55; f = ifd.f; printf("%f %f\n", ifd.f, f); printf("%d\n", ifd.i); } |
So, when we use ifd.i, this means we can use ifd as an integer. When we use ifd.f, we can use ifd as a floating point number. However, unlike a struct, we can't use both ifd.i and ifd.f at the same time. This is because ifd is not an aggregate data type with three fields -- it is a type that holds one value, which can be any of three types -- integer, float or double.
So, when we look at the above code, the first three lines say:
Here's the output -- note the last line -- indeed, using ifd.i when you have set ifd.f gives you something that is not very useful:
UNIX> union1 1 1 5.550000 5.550000 1085381018 UNIX>
int 3 float -4.33 string Jim int -67 float 40000.1Now, we could structure this code in one of two ways. First, lets write it without using unions. We can have a struct for each item, and this struct has four fields: type, i, f and s. The type field is a character that identifies the type as either 'i' for integer, 'f' for float, and 's' for string. Then if type is 'i', then the i field contains the value. If type is 'f', then the f field contains the value. If type is 's', then the s field contains the value.
Here's the code (this is in union2.c):
#include <stdio.h> #include <string.h> #include "fields.h" typedef struct { char type; int i; float f; char *s; } Item; main() { Item array[5]; int i; IS is; is = new_inputstruct(NULL); /* Read in the items -- if "int", read it into array[i].i if "float", read it into array[i].f if "string", read it into array[i].s */ for (i = 0; i < 5; i++) { if (get_line(is) != 2) exit(1); if (strcmp(is->fields[0], "int") == 0) { array[i].type = 'i'; if (sscanf(is->fields[1], "%d", &(array[i].i)) != 1) exit(1); } else if (strcmp(is->fields[0], "float") == 0) { array[i].type = 'f'; if (sscanf(is->fields[1], "%f", &(array[i].f)) != 1) exit(1); } else if (strcmp(is->fields[0], "string") == 0) { array[i].type = 's'; array[i].s = strdup(is->fields[1]); } else { exit(1); } } /* Write out the items. */ for (i = 0; i < 5; i++) { printf("Item %d: Type %c -- ", i, array[i].type); if (array[i].type == 'i') { printf("Value: %d\n", array[i].i); } else if (array[i].type == 'f') { printf("Value: %f\n", array[i].f); } else if (array[i].type == 's') { printf("Value: %s\n", array[i].s); } else { exit(1); } } /* Print the size of the item struct */ printf("\n"); printf("Sizeof(Item): %d\n", sizeof(Item)); } |
Now, when you give it the example input above, you get: (I've bold-faced the input):
UNIX> union2 int 3 float -4.33 string Jim int -67 float 40000.1 Item 0: Type i -- Value: 3 Item 1: Type f -- Value: -4.330000 Item 2: Type s -- Value: Jim Item 3: Type i -- Value: -67 Item 4: Type f -- Value: 40000.101562 Sizeof(Item): 16 UNIX>A little yucky, but certainly code that you all are capable of writing. One of the problems with this code is how wasteful it is. Every item contains two fields (8 bytes) that it does not use. Of course, in this example, that leads to a whopping 40 bytes, so no, it's not a problem here. But if we had a million items, then it would be 8 megabytes worth of wasted space.
The solution is to use a union for the value of the item. The code is in union3.c. Note how similar it is to union2.c:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "fields.h" typedef struct { char type; union { int i; float f; char *s; } value; } Item; main() { Item array[5]; int i; IS is; is = new_inputstruct(NULL); /* Read in the items -- if "int", read it into array[i].i if "float", read it into array[i].f if "string", read it into array[i].s */ for (i = 0; i < 5; i++) { if (get_line(is) != 2) exit(1); if (strcmp(is->fields[0], "int") == 0) { array[i].type = 'i'; if (sscanf(is->fields[1], "%d", &(array[i].value.i)) != 1) exit(1); } else if (strcmp(is->fields[0], "float") == 0) { array[i].type = 'f'; if (sscanf(is->fields[1], "%f", &(array[i].value.f)) != 1) exit(1); } else if (strcmp(is->fields[0], "string") == 0) { array[i].type = 's'; array[i].value.s = strdup(is->fields[1]); } else { exit(1); } } /* Write out the items. */ for (i = 0; i < 5; i++) { printf("Item %d: Type %c -- ", i, array[i].type); if (array[i].type == 'i') { printf("Value: %d\n", array[i].value.i); } else if (array[i].type == 'f') { printf("Value: %f\n", array[i].value.f); } else if (array[i].type == 's') { printf("Value: %s\n", array[i].value.s); } else { exit(1); } } /* Print the size of the item struct */ printf("\n"); printf("Sizeof(Item): %d\n", sizeof(Item)); } |
The output is the same, only now you'll notice that the size of an item is only 8 bytes:
UNIX> union3 int 3 float -4.33 string Jim int -67 float 40000.1 Item 0: Type i -- Value: 3 Item 1: Type f -- Value: -4.330000 Item 2: Type s -- Value: Jim Item 3: Type i -- Value: -67 Item 4: Type f -- Value: 40000.101562 Sizeof(Item): 8 UNIX>This is because the union only allocates enough space for its biggest field -- here they are all 4 bytes, so the union is just four bytes.