Tagging a Union

A union by itself presents some issues, which is why unions are flagged as insecure. I’ve shown some examples in last week’s Lesson. But there is a solution that provides a backstop for consistency when accessing the different data types stored as union members.

I received a few slaps on LinkedIn when I shared the “Back When Unions Were Useful” post a few weeks back. Apparently, modern C code still uses a union in many instances. Active C programmers descended upon my post with ample vigor. The key that keeps a union usable and necessary is that the union’s data must be tagged.

The process of tagging a union involves coordinating a union’s contents with an enumerated list of data types. The list of data types determines how the union’s data is accessed. As an example, consider this union:

union values {
    char ch[8];
    long li;
};

The values union has storage for char variable ch as an 8-character array or string. Also present is a long int, li. Both of these items share their storage, which is a point of contention.

To tag these items, an enumerated list is coupled to the union. In the enumeration, the data types are represented as constant values:

enum {
    TYPE_STRING,
    TYPE_LONG
};

The enum keyword creates two constants, TYPE_STRING and TYPE_LONG. These constants are assigned values 0 and 1 by the compiler, which is how enumeration works. The point is to relate these constants to the union’s data types. This pairing is creates the tags.

The enum and the union are married within a structure: the constants are the tags, and the union’s data is the payload. This design keeps data aligned, with the tags serving as a reference to ensure that the proper values stored in the union are recognized. The following code shows an example:

2026_10_10-Lesson.c

#include <stdio.h>
#include <string.h>

/* tagged union definition */
struct u_data {
    /* tags */
    enum {
        TYPE_STRING,
        TYPE_LONG
    } data_type;
    /* payload */
    union values {
        char ch[8];
        long li;
    } v;
};

/* generate output based on tag */
void output(struct u_data d)
{
    switch(d.data_type)
    {
        case TYPE_STRING:
            printf("String value is: %s\n",d.v.ch);
            break;
        case TYPE_LONG:
            printf("Long int value: %li\n",d.v.li);
            break;
        default:
            printf("Unknown data type\n");
    }
}

int main()
{
    struct u_data data;

    /* set data type tag and data */
    data.data_type = TYPE_STRING;
    strcpy(data.v.ch,"Hello!");

    /* output proper data */
    output(data);

    return 0;
}

The code starts with the tagged union structure definition. The u_data structure contains enumerated constants data_type and union values. The constant names in the enumeration pair with the data types stored in the union.

The output() function swallows a u_data structure as its argument, referenced as d within the function. A switch-case structure evaluates the d.data_type member in the structure, which reflects the enumerated constant for the data type requested. Each case statement matches the data types. A printf() statement outputs the data properly based on the tag presented:

  • d.v.ch is the structure member for a string. d is the structure passed to the output() function. v is the union stored in the structure. ch is the string stored in the union.
  • d.v.li is the structure member for a long int. d is the structure passed to the output() function. v is the union stored in the structure. li is the long integer stored in the union.

The main() function creates a u_data structure named data: struct u_data data;

Before a value is set in the union, a tag is assigned:

data.data_type = TYPE_STRING;

This tag helps the programmer and the code to identify the union’s data as a string. Next, the string is assigned:

strcpy(data.v.ch,"Hello!");

Finally, a call is made to the output() function to properly deal with the union’s contents:

String value is: Hello!

Tags work to ensure that union data is accurately interpreted. Like many other aspects of C programming, adhering to this rule is up to the programmer; the compiler doesn’t check for consistency with regards to data stored in a union, which is a point I’ve been driving home for the past few weeks. But if you assign tags and use them, unions present an interesting and useful solution to many coding problems.

By the way, if you change the code in the main() function to read:

data.data_type = TYPE_LONG;

The output reads:

Long int value: 36762444129608

Again, the code runs properly only when the programmer is paying attention.

Next week, I continue my exploration of unions by exploiting their primary weakness.

Leave a Reply