AR

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.

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, and variable(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, nested struct, and union.

    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 of expr2; else the result is the evaluation of expr3.

  • 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”