CMPSC 311 - Types, Structs, and Unions
Types
- A data type is an abstraction that allows a programmer to treat different memory ranges as different classes (types) of data.
- Examples: integers, real numbers, text, records.
- Variables act as aliases for memory ranges.
- The compiler uses type information to determine how to apply logical operations.
- Defines how different variables can be operated on and which machine instructions are generated and executed.
- All programming languages use a type system to interpret code.
C Typing
- Programming languages are strongly or weakly "typed".
- This refers to the language's quality in dealing with ambiguity in types and usage.
- C is weakly typed, leading to flexibility but also bugs.
- Strongly typed languages won’t compile if variables of different types are passed as parameters or compared without explicit conversion.
- Weakly typed languages like C will attempt to coerce (convert) types.
Implications of Weak Typing
- Be careful when dealing with expressions, parameter passing, and comparisons.
- Example:
- A
double
variable divided by an integer may yield unexpected results due to implicit type conversion.
- A
Static vs Dynamic Typing
- Static typing: type of data is decided at compile time, and type conversion occurs then.
- Examples: C, C++, Java
- Dynamic typing: run-time environment dynamically applies types to variables as needed.
- Examples: Perl, Python, Ruby
Type Casting
- Type casting allows explicit control over type conversion.
- Syntax:
(type)variable
- Changes the semantics of the expression to be all integers.
- Example:
c double one = 3.24; if ((int)one / 3 == 1) { printf(“true”); } else { printf(“false”); }
Legal Type Casting
- Demonstrates type casting with short int, long int, float, double, and char variables.
- Output shows the results of casting these variables to different types.
Defining Types: typedef
- The
typedef
keyword extends the C type system by declaring new types for the compiler to recognize and use. - Syntax:
typedef [old type] [new type];
- Example:
c typedef unsigned char bitfield;
Using User-Defined Types
New types can be used anywhere built-in types are used.
Example:
typedef unsigned char bitfield; bitfield myFunction(bitfield x, int y) { bitfield z; float a; return ((bitfield)1); }
The compiler treats the new type exactly as the old type (the new name acts simply as an alias for the original type).
Programming 101: When to Define Your Own Types
- Define your own types when working on a system that:
- Will have a lot of code
- Will last a long time
- Needs to be ported
- Have multiple revisions
- Have a maintenance life time
- Relies on standards
Structures
A structure is an organized unit of data that is treated as a single entity (variable).
Can be defined like any other variable and has an implicit “type”.
Syntax:
struct { [definition] } [variable(s)];
where
definition
is the layout of the structure, andvariable(s)
are variables of the struct type.
A Basic Example
struct {
char name[128]; // Make and model
int mileage; // The current mileage
} gremlin, cayman, cessna180, montauk;
Referencing Fields in a C Struct
The period "." is used to reference the fields of the struct.
strcpy(cayman.name, “My favorite car”); // strcpy will be covered later cayman.mileage = 1240;
Enumerated Types
Allow associating integer values with names.
Syntax:
enum { <ID1> = <value>, <ID2> = <value>, ... } variable;
Example:
enum { SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATURDAY = 6 } daysOfWeek;
Enumerated Types (continued)
The
<value>
part of the declaration is optional. If omitted, the compiler assigns integers to the values starting from 0.Example:
enum { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } daysOfWeek;
Using Enumerated Types
You can use the names in any place that you would use an integer.
int x = MONDAY; int y = TUESDAY * FRIDAY; if (day == SATURDAY) { ... } int func(int x); ... func(TUESDAY);
Complex Structures
struct {
enum {
AUTOMOTIVE = 0, // Automobile or equivalent
AERONAUTICAL = 1, // Airplane, rotorcraft, ..
MARINE = 2 // Boat or similar
} type;
char name[128]; // Make and model
int milage; // The current milage
} gremlin, cayman, cessna180, montauk;
// Example
cayman.type = AUTOMOTIVE;
if (cayman.type == AUTOMOTIVE) {
printf(“This is a car\n”);
}
Nested Structures
struct {
enum {
AUTOMOTIVE = 0, // Automobile or equivalent
AERONAUTICAL = 1, // Airplane, rotorcraft, ..
MARINE = 2 // Boat or similar
} type;
char name[128]; // Make and model
int milage; // The current milage
struct {
int cylinders; // The number of cylinders
int horsepower; // The total horsepower
int hours_smoh; // Hours since last major overhaul
} engine; // Engine Specification/history
} gremlin, cayman, cessna180, montauk;
// Example
cayman.engine.cylinders = 6;
cayman.milage = 1240;
Unions
A union is a way to overlay different data structures over the same memory region.
It allows selectively interpreting the data in place.
Syntax:
union { [definition] } [variable(s)];
Example:
union { char vin[17]; // Vehicle ID (car) char tail_number[8]; // Tail number (airplane) char hull_id[12]; // Hull ID (boat) } vehicle_id; // The vehicle identifier
Unions (continued)
union {
char vin[17]; // Vehicle ID (car)
char tail_number[8]; // Tail number (airplane)
char hull_id[12]; // Hull ID (boat)
} vehicle_id; // The vehicle identifier
// Example
strcpy(vehicle_id.vin, “123456”); // strcpy will be covered later
printf(”%s\n”, vehicle_id.vin); // prints “123456”
vehicle_id.tail_number = “F-BOZQ”;
printf(”%s\n”, vehicle_id.vin); // prints “F-BOZQ”
printf(“Size:%lu\n”, sizeof(vehicle_id)); // prints “17”
Bringing it Together
Example of a complex structure using
enum
, nestedstruct
, andunion
.struct { enum { AUTOMOTIVE = 0, // Automobile or equivalent AERONAUTICAL = 1, // Airplane, rotorcraft, .. MARINE = 2 // Boat or similar } type; char name[128]; // Make and model int milage; // The current milage struct { int cylinders; // The number of cylinders int horsepower; // The total horsepower int hours_smoh; // Hours since last major overhaul } engine; // Engine Specification/history union { char vin[17]; // Vehicle ID (car) char tail_number[8]; // Tail number (airplane) char hull_id[12]; // Hull ID (boat) } vehicle_id; // The vehicle identifier } gremlin, cayman, cessna180, montauk;
The Role of Types
Using
typedef
to define vehicle information.typedef enum { AUTOMOTIVE = 0, // Automobile or equivalent AERONAUTICAL = 1, // Airplane, rotorcraft, .. MARINE = 2 // Boat or similar } VEHICLE_TYPE; typedef struct { int cylinders; // The number of cylinders int horsepower; // The total horsepower int hours_smoh; // Hours since last major overhaul } ENGINE_INFO; // Engine specification/history typedef union { char vin[17]; // Vehicle ID (car) char tail_number[8]; // Tail number (airplane) char hull_id[12]; // Hull ID (boat) } VEHICLE_IDENT; // The vehicle identifier // Vehicle structure typedef struct { char name[128]; // Make and model int milage; // The current milage VEHICLE_TYPE type; // The type of vehicle ENGINE_INFO engine; // Engine specification/history VEHICLE_IDENT vehicle_id; // The vehicle identification } VEHICLE; // Now define the variables VEHICLE gremlin, cayman, cessna180, montauk;
Accessing Fields by Pointer (Dereferencing)
When handling a pointer to a struct, the fields are accessed with the
->
operator instead of the.
operator.VEHICLE cayman; VEHICLE *vehicle = &cayman; strcpy(vehicle->name, “2013 Porsche Cayman S”); vehicle->engine.cylinders = 6;
Conditional Operator
Consists of two symbols
?
and:
used as follows:expr1 ? expr2 : expr3
Read as "if expr1 then expr2 else expr3".
expr1
is evaluated first; if its value isn’t zero, the result is the evaluation ofexpr2
; else the result is the evaluation ofexpr3
.Common usage in
printf
.int i = 1, j = 2, k; k = i > j ? i : j; k = (i >= 0 ? i : 0) + j; if (i > j) printf(“%d\n”, i); else printf(“%d\n”, i); //or printf(“%d\n”, i > j ? i : j);
Working with Structs
Example of multiline print string and "?" expressions.
VEHICLE *vehicle = &cayman; printf( "*** Vehicle Information **\n" "Name : %s\n" "Milage : %u\n" "Vehicle type : %s\n" "Cylinders : %u\n" "Horsepower : %u hp\n" "SMOH : %u hours\n" "VIN : %s\n", vehicle->name, vehicle->milage, (vehicle->type == AUTOMOTIVE) ? "car" : (vehicle->type == AERONAUTICAL) ? "airplane" : "boat", vehicle->engine.cylinders, vehicle->engine.horsepower, vehicle->engine.hours_smoh, (vehicle->type == AUTOMOTIVE) ? vehicle->vehicle_id.vin : (vehicle->type == AERONAUTICAL) ? vehicle->vehicle_id.tail_number : vehicle->vehicle_id.hull_id );
How is the Memory Laid Out?
- Using
#define MEM_OFFSET(a, b) ((unsigned long)&b)-((unsigned long)&a)
to print out the values of the fields.
Memory Layout Example
- Output shows the sizes, addresses, and offsets of the fields in the
cayman
struct. - Compiler padding is introduced to ensure that the size aligns with a multiple of the machine word size since many ISA instructions require the operable address to loadable from a word location.
- Compiler padding is dependent on the underlying processor architecture, so beware when working with data from other computers.
Copy by Assignment
You can assign the value of a struct from a struct of the same type; this copies the entire contents.
Example:
#include <stdio.h> struct Point { float x, y; }; int main(int argc, char **argv) { struct Point p1 = {0.0, 2.0}; struct Point p2 = {4.0, 6.0}; printf("p1: {%f,%f} p2: {%f,%f}\n", p1.x, p1.y, p2.x, p2.y); p2 = p1; printf("p1: {%f,%f} p2: {%f,%f}\n", p1.x, p1.y, p2.x, p2.y); return 0; }
Returning Structs
You can return structs from functions.
Example (Complex Number):
```c
// a complex number is a + bi
typedef struct complex_st {
double real; // real component (i.e., a)
double imag; // imaginary component (i.e., b)
} Complex, *ComplexPtr;
Complex AddComplex(Complex x, Complex y) {
Complex retval;
retval.real = x.real + y.real;
retval.imag = x.imag + y.imag;
return retval; // returns a copy of retval
}
Complex MultiplyComplex(Complex x, Complex y) {
Complex retval;
retval.real = (x.real * y.real) - (x.imag * y.imag);
retval.imag = (x.imag * y.real) - (x.real * y.imag);
return retval;
}
```
Bit Fields
Create numeric (integer) values that have a very specific width (in bits).
C supports this by identifying the bit width in declarations of integer fields.
Example:
struct vehicle_props { uint32_t registered : 1; uint32_t color_code : 8; uint32_t doors : 3; uint32_t year : 16; } props; props.registered = 1; props.color_code = 14; props.doors = 2; props.doors = 9; // Legal, but out of range props.year = 2013; printf(“Size of props: %lu\n”, sizeof(props)); // Prints “4”