290 likes | 430 Views
CS1336, Structs, Unions and Enums. A lecture for you while I'm gone. Outline. Review of Array vs Struct Structs and memroy Union Enumerated values. Structs vs Arrays. What's an array? A bunch of items of the same type one after another in memory.
E N D
CS1336, Structs, Unions and Enums A lecture for you while I'm gone
Outline • Review of Array vs Struct • Structs and memroy • Union • Enumerated values
Structs vs Arrays • What's an array? • A bunch of items of the same type one after another in memory. • Useful for keeping up with lists of similar, related things • Each item is called an element • We get to each element using its integer index. • There's little difference between an array and a pointer to an array (but some difference)
Structs vs Arrays • What's a struct • A bunch of items of (potentially) different types one after another in memory. • Useful for keeping up with related, but potentially dissimilar things • Items in a struct are called fields • We get to the fields of a struct using their names • Different syntax for a struct and a pointer to a struct: • For a struct: a.color = red; • For a pointer to a struct: a->color = red;
Making Structure Instances • We use the term, instance, to talk about a value of some struct type that's been allocated in memory. • We can make an instance of MyStruct on the runtime stack with:# Goes away when the variable goes out of scope.struct MyStruct a; • We can dynamically allocate an instance of MyStruct with:# doesn't go away until we free it.struct MyStruct *a = (struct MyStruct *)malloc( sizeof( struct MyStruct ) );
Structs and Binary I/O • An instance of a struct occupies a contiguous region of memory • We should be able to read an write the whole thing by writing its memory • struct MyStruct a; • fread( &a, sizeof( struct MyStruct ), 1, fp ); • fwrite( &a, sizeof( struct MyStruct ), 1, fp ); • Just like with primitive types, the file here will contain binary data reflecting what the structure looks like in memory.
Structs and Binary I/O • Binary I/O of entire structs is convenient, but it has some consequences • May be non-portable between different system architectures because of byte ordering issues. • Depending on the compiler and the system architecture, a struct instance may contain unused bytes called padding • This padding wastes space in the file and, since it may depend on the compiler, it reduces portability
Padding in a Struct • Most processors are more efficient if or require that you are careful about what kind memory address a value starts at. • For example, many systems expect an int to start at an even byte boundary. • It would be ok for an int to be stored at addresses 0xffe8, 0xb43c2a or 0x3ccb0 • It would not be ok for an int to be stored at address 0xff, 0xb43c2b or 0x9207 • We say some systems require a value to be aligned in memory • Alignment requirements typically only apply to primitive types that are larger than a byte (int, foat, double, long int, short, etc.)
Padding in a Struct • We say a value is naturally aligned if it starts at an address that's a multiple of its size. • For example, a 4-byte int would be naturally aligned if it started at an address that's a multiple of 4 • A 2-byte short would be naturally aligned if it started on an even numbered address • A compiler may align values in a struct either because the system requires it or because it will make the code faster.
Padding in a Struct • Aligning the elements of a struct may leave spaces between them • Pretend this struct will be compiled on a system that requires values larger than a byte to be even-byte aligned. struct MyStruct { char a; /* 1-byte field. */ int b; /* 4-byte field. */ char c; /* 1-byte field. */ short d; /* 2-byte field. */ } • The compiler will have to introduce a byte of padding between after each char, just to make sure the next field is properly aligned.
Padding in a Struct • Even-byte padding example, continued. • The struct will end up looking like this (the pad fields aren't really there, I'm just showing where the compiler will put extra space): struct MyStruct { char a; /* 1-byte field. */ char pad; /* 1 byte of padding. */ int b; /* 4-byte field. */ char c; /* 1-byte field. */ char pad; /* 1 byte of padding. */ short d; /* 2-byte field. */ } • Because of the padding, sizeof will report 10 bytes for this struct even though we only need to store 8 bytes of data. • On a system that required ints to be naturally aligned, this struct would receive even more padding • For example, my laptop wants the b to be 4-byte aligned.
Structs and Padding • We have some influence over how much padding occurs. • We could reorder fields to reduce padding. struct MyStruct { int b; /* 4-byte field. */ char a; /* 1-byte field. */ char c; /* 1-byte field. */ short d; /* 2-byte field. */ } • Here, no padding would be required on a system that expected even-byte alignment.
Alternatives to Binary Struct I/O • Reading and writing whole structure instances in binary is convenient, but it may waste space because of padding and may inhibit portability • Still, lots of software systems do this kind of thing when these issues are not important • We have some alternatives • Read and write each field one after another in some human readable format • Could use printf/scanf • File would be human readable, which is nice • No extra padding will be required • Format would be very portable • However, a human readable format often requires more storage • Consider, the int 2458154370 is 10 ASCII characters, but the would require only 4 bytes if written to a binary file.
Alternatives to Binary Struct I/O • We have some alternatives, continued • Read and write each field one after another in binary • Could use fread/fwrite for each field. • No extra padding will be required • Encoding of each field would typically be cheap (in storage) • We would have to consider portability issues like byte order for each field
Inspecting a Struct • We can use the sizeof() operator to find out how much memory a struct instance requires • We can also do some pointer tricks to find out where each of its fields start inside the instance (the offset for each field) • Pretend MyStruct is a struct • We can take a null pointer and pretend it's a pointer to a MyStruct instance:(struct MyStruct *)NULL • We can talk about a field of this MyStruct instance starting at memory address zero:((struct MyStruct *)NULL)->myField • We can talk about the address of myField in a structure starting at memory address zero:&(((struct MyStruct *)NULL)->myField) • If we cast this to an int, we can print out the offset of myField in an instance of MyStruct(int) &((struct MyStruct *)NULL)->myField
Union • We have been thinking about how something like a struct instance is organized in memory. • Now is a good time to think about union • Syntax for union is almost the same as struct:union Example { int a; char b; double c;} • However, instead of laying out its fields one after another in memory, the union organizes them one over another in memory • Every union field starts at the same offset, just the start of the structure • Memory size of the union is the size of it's largest field. • We can check using the trick we already figured out.(int) &(((union Example *)NULL)->b) == 0
Why use Union • Union has a strange behavior and it's not used as much as struct • It is useful when: • You don't know you will sometimes have to store a value of one type and sometimes a value of a different type • but you won't have to store both values at the same time • You want to save memory by using the same memory to store both. • Union really just gives us multiple ways of looking at or using the same memory
A Use for Union • Consider the union:union Integer { int ival; unsigned char bval[ sizeof( int ) ]; }; • Here, the int and character arrays are really different ways to get to the same memory. • This would make it easy to work with an int stored in ival either as a whole int or a byte at a time./* Put a value into our integer. */ union Integer i; i.ival = 0xffb0; /* Extract its representation a byte at a time. */ int j; for ( j = 0; j < sizeof( int ); j++ ) printf( "%d ", i.bval[ j ] ); printf( "\n" );
Union inside Struct • Union is most often used inside a struct • This gives us a way to make a struct that be used to hold similar, but different things • The following struct can be used to store either an int, a float and a double or an int, a float and a short string.struct MyVariantStruct { /* First two fields are always there. */ int a; float b; /* Variant part of the struct, can contain a double or a short string. */ union VPart { double c; char d[ 10 ]; } vpart;};
Union in a Struct • Syntax for this example is a little difficult • Inside the structure, we are defining a new union type called VPart. • At the end of the VPart definition, we add a new field of type VPart to the struct, the field is called vpart • We can get to the fields of a MyVariantStruct instance just like you would expect:MyVariantStruct bob;bob.a = 20;bob.b = -283.6;/* Get to the union in bob and use its double field */bob.vpart.c = 3.1415;/* Get to the union in bob and use its character array field. This will overwrite what we just stored in c. */strcpy( bob.vpart.d, "Hello!" ); • Consider, what would the size of MyVariantStruct be?
Anonymous Unions • Union inside a struct is so common, we can use an abbreviated syntax for it. • We can leave out the type name and the field name for the union inside our struct.struct MyVariantStruct { /* First two fields are always there. */ int a; float b; /* This is called an anonymous union */ union { double c; char d[ 10 ]; };};
Anonymous Unions • Now, we can use shorter syntax to get to the variant part of the structureMyVariantStruct bob;bob.a = 20;bob.b = -283.6;/* Write to the double field in bob's union */bob.c = 3.1415;/* Write to the character field in bob's union */strcpy( bob.d, "Hello!" );
Enumerated Types • You probably saw this in CS1, but it's on my list of stuff to teach. • An enumerated type is a way to make your own type containing a specific set of values that you pick • An enumerated type is a lot like an int, and is even represented as numbers in the compiled code • Example:/* Define a new type called Color, with 7 colors as its only legal values. */enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET };/* Show off how we can use this type. */Color favorite = GREEN;if ( favorite != RED ) printf( "You have the wrong favorite color\n" ); • Values in an enumeration are usually given in all capitals, since they are like constants • Values in an enumeration automatically promote to int
Enumerated Types • Enum is represented as a number • By default, the first value in the definition (RED on the previous slide) gets represented by zero. • Subsequent values in the enumeration are represented by the next integer • So, YELLOW is represented by 2 and VIOLET is represented by 6 • You will see this number if you try to print a variable of some enum type:printf( "%d\n", favorite ); • You can assign directly from an int (requires a type cast in C++)enum Color favorite = (enum Color) 3;
Fun with Enum • You can pick the numbers used to represent values in your enumerated typeenum Color { RED = 1, ORANGE = 3, VIOLET = 6, BLUE, GREEN = 0, PURPLE }; • Here, BLUE will be represented by 7 since enum values are get the value one after their predecessor. • Notice that PURPLE and RED will both be represented by 1. This will make them indistinguishable. Here, they are just two names for the same value.
Uses for Enum • It's unfortunate that enumerated values print as numbers rather than their names. • Likewise, the user can't type the name of an enumerated value as program input • The way you should think of enum is as a mechanism for making your program more readable and for using a more specific type than int where possible • Using an enumerated type where you can may make it easier for someone else to understand your program • An enumerated type as a function parameter makes it easier to make sure you have called the function with a meaningful value
Examples of Enum Use • Enum can be particularly appropriate for values that will govern a switch statement:enum Direction { NORTH, SOUTH, EAST, WEST };…void move( Direction d ) { switch ( d ) { case NORTH: /* Do something useful. */ break; case SOUTH: /* Do something else. */ break; ... }}