7. Custom Data Types

With custom data types, we prefer to define structs and enums in typedefs rather than plain struct or enum definitions. Since there are 2 legal ways to do it in C, we simply prefer the way that makes it explicit that what is being defined is a custom data type: with a typedef.

It is policy that custom data type names always end with a “_t” to make it clear in code that it is the name of a data type.

7.1. <abbreviated_type_name> Prefixes for Custom Data Types

When we create variables based on custom data types, the <abbreviated_type_name> portion of the variable names is typically an abbreviated form of the name of the custom data type.

Example for custom data type vector2_t: gvect2CurrentHeading or gv2CurrentHeading.

If object is a generic item of that type (i.e. a formal argument declared in the receiving function that doesn’t care about its application (higher level) meaning to the caller (which might be something like “finish line” or “heading” to the caller), the receiving function merely knows it as a vector and does vector math with it), then instead of calling the argument or variable name avect2Vector2 or apvect2Vector2 (which is redundant), the TYPE Prefix is dropped and the name indicates the type, since this serves the purpose of the prefix. Example: lTempVector2 (local temporary vector2_t) or apVector2 or apVect2 (argument pointer to a vector2_t), or even v1 and v2 in alignment with Prefix Exceptions.

These are abbreviated-type-prefixes for some object-types in some GPS applications:

7.1.1. Custom Type Prefix Examples

Abbreviation

Type

Description

g

Gate_t

Gate

pg

PredictionGate_t

Prediction Gate

vg

VenueGate_t

Venue Gate

mg

MiniGate_t

Compressed VenueGate_t

fix

gps_fix_t

GPS fix

fix

packed_gps_fix_t

GPS fix using integers

loc

location_t

Location

ploc

packed_location_t

Location using integers

dt

geo_date_t

Date

dtm

geo_date_time_t

Date/Time

7.1.2. Spelled-Out Examples

Variable

Type

Description

lMGate

MiniGate_t

Compressed form of VenueGate_t

lMiniGate

MiniGate_t

Alternative name for the above

lpMGate

* MiniGate_t

Pointer to MiniGate_t

lpMiniGate

* MiniGate_t

Alternative name for the above

7.2. Other Policies with structs and enums

One thing we also do with structs:

It is occasionally necessary to use a type that has only partially been defined (from the compiler’s viewpoint) for example as a pointer field to the same type of struct that is being defined (e.g. this happens in tree structures and linked-list nodes). Thus, this policy has been found to be important for consistency, and also for utility when such a situation is encountered: for struct’s, specify the template name in the typedef. Instead of doing this some of the time when it is needed, we do it all the time for consistency. The suffix “_template” is used verbatim. (In [KR1988], Brian Kernighan calls it a “tag”. I prefer the term “template” since it more closely describes what it represents.)

typedef struct MyType_template {
    type                      i...;
    type                      i...;
    type                      i...;
    ...(fields)...
    struct MyType_template  * ipNextNode;
    struct MyType_template  * ipPreviousNode;
} MyType_t;

Note that in the struct field names, they always contain an ‘i’ (instance) scope prefix (as covered above in the Variable Names section). Example:

uint16_t  iui16HeadingDegreesAzimuthX10;

On the other hand, for enum’s, we do not specify the template name, even though it is an option in C syntax to do so. (Reason: in my C career, I have never used enum tag/template names and have never found a need for them.)

typedef enum {
    eQuadrant_northeast,
    eQuadrant_southeast,
    eQuadrant_southwest,
    eQuadrant_northwest
} eQuadrant_t;

There are 2 things to note about this:

  1. The type name itself is prefixed with an ‘e’ to denote that it is an enumeration. This keeps it clear in the code that the type is most likely an int, depending on the compiler. Note that when the number of bits involved is important, this clarification helps you to very quickly recognize the actual underlying data type, thus helping to avoid mistakes.

    You can name variables and struct fields with this data type, and it is often helpful to do so when the size of the field across platforms does not matter (i.e. an int is okay). This can be helpful because the compiler does type checking and often provides helpful messages if an enum type is violated (e.g. hard-coded value assigned to it rather than one of the enums elements):

    typedef struct MyStruct_template {
        ...(fields)...
        eQuadrant_t  ieQuadrant;
        ...(fields)...
    } eMyStruct_t
    

    However, if what you really wanted to do was only occupy a single 8-bit byte, then you will have to declare it differently, like this:

    typedef struct MyStruct_template {
        ...(fields)...
        uint8_t      iui8Quadrant;
        ...(fields)...
    } eMyStruct_t
    

    And then:

    gMyStruct.iui8Quadrant = eQuadrant_southeast;
    

    is legal and normally will not cause any compilation errors. Simultaneously, and this is the important part, it is crystal clear what is going on there. Nothing is hidden, and that’s how we like it — bugs cannot hide in that code.

  2. Also note in the enum element names, they are always prefixed by the type name minus the ‘t’ at the end. The reason for this policy was due to the need to share long and critical enumerations between firmware projects in C and software projects (e.g. exchanging messages with the firmware) in VB.NET and C#. And while VB.NET and C# don’t strictly need these prefixes, because the type name always precedes the enumeration element name like this:

    eMyType_t.eMyType_description_2
    

    when that same enumeration is shared with C, the enumeration element is named as:

    eMyType_description_2
    

    without the ‘eMyType_t.’ type prefix as required in C# and VB.NET. The choice was a trade-off between clarity of meaning in C vs redundancy in VB.NET and C#. The latter was chosen in order to preserve the clarity of meaning in C, at the expense of some minor redundancy in C# and VB.NET.

When defining or declaring custom-type variables, prefer to so with the type name, not the template name. Example:

/* Do this. */
eMyStruct_t  gMapArea;

not:

/* Don't do this. */
struct MyStruct_template  gMapArea;

Even though both are legal, we prefer the former. The one exception was named above: when a pointer to the struct being defined is required within that struct, such as happens in tree and linked-list structures. In that case it is done like this:

typedef struct AnotherType_template {
    ...(fields)...
    struct AnotherType_template  * ipMapArea;
    ...(fields)...
} AnotherType_t;

since there is no other legal way to do it in C.

7.3. Alignment of Field Names In structs

For readability, type names of fields are aligned in one column, and the field names themselves are aligned in another column. The whitespace between them is a minimum of 2 spaces after the longest type name, and before either the * if there are pointers in the struct, or just 2 spaces if there are no pointers.

Examples:

/** -----------------------------------------------------------------------
 * \brief  Packed GPS Fix
 *//*----------------------------------------------------------------------*/
__EXTENSION
typedef struct packed_gps_fix_template {
    gps_fix_crossing_data_t  iCrossingData;                          /* 20 bytes, 20 subtotal */
    ublox_lat_lon_t          iUbloxLatLon;                           /*  8 bytes, 28 subtotal */
        /**< Mult by 0.0000001 to get Lat/Lon in degrees. */
    packed_vector_t          ipvectHorizontalRatesInCmPerFix;        /*  8 bytes, 36 subtotal */
        /**< ii32_X_cm - East Rate in Packed Location distance per fix
         *   ii32_Y_cm - North Rate in Packed Location distance per fix */
    int32_t                  ii32HeightAboveMeanSeaLevelInMm;        /*  4 bytes, 40 subtotal */
        /**< Mult by 0.001 to get meters. */

    ...(remaining fields omitted for brevity)...
} packed_gps_fix_t;
    /**< 68 bytes.  Time to copy:  34 SYSCLKs. */


/** -----------------------------------------------------------------------
 * \brief  Auto-Finish-Line Determination State Data
 *//*----------------------------------------------------------------------*/
typedef struct AutoSetupState_template {
    packed_location_t       iplocBreadcrumbTrailHead;
    Gate_t                  iTestGate;
        /**< This gate is placed on top of breadcrumbs that are similar in direction and
         *   close enough to make them interesting. */

    Gate_t                * ipGateCurrentlyInFocus;
        /**< This gate points to ``iTestGate`` when crossing it might be imminent. */

    mEDSMEM breadcrumb_t  * ibcpBreadcrumbInspectionLimitPointer;
        /**< Note that this is a POINTER.  (Faster than indexes the way we are using them.)
         *   While we are inspecting Breadcrumbs, looking to find
         *   a trail we have already traversed, periodically we
         *   sort and mark a limit to the number of Breadcrumbs
         *   we want to inspect based on proximity.  That limit
         *   may vary depending on the pattern the Breadcrumbs are in,
         *   so we compute it dynamically after every sort.  This is
         *   where that limit is stored.  It is a pointer so that
         *   we can rapidly compare it with the pointer being used
         *   to inspect Breadcrumbs. */

    breadcrumb_t            ibcCurrentLocation;
    uint16_t                iui16BreadcrumbCount;
        /**< Number of items in gGenericArray[] */

    ...(remaining fields omitted for brevity)...
} AutoSetupState_t;

If not already clear, there is always at least 2 spaces in front of the * and always 1 space after it before the field name. This is for readability.