Back When Unions Were Useful

Though it’s shunned today, using a union in your C code was common in the 1980s. Back then, compilers and IDEs such as Borland’s TurboC were customized for the MS-DOS operating system. Daring C programmers used the C language’s union keyword to access the system’s hardware directly. Scary.

Hardware meddling, specifically accessing the CPU’s registers directly from within your C code, was possible because your program was the only thing running on the computer at the time; calling or responding to an 8086 interrupt was okee-doke. So loading up a few registers via a union wasn’t something that made other programs go nuts.

To make the magic happen, the C source code included the dos.h header file. This header file prototyped a few handy but DOS-specific functions as well as various defined constants and other stuff useful to that operating system. Remember that this era was when the K&R version of C was the only standard. It was years before ANSI C came about. In fact, the chaos of C versions and varieties is what prompted the development of the POSIX standard in 1988.

In the dos.h header file was the definition for a union you could load up with values for low-level system access:

union REGS {
    struct  WORDREGS x;
    struct  BYTEREGS h;
};

The union contains two structures, WORDREGS and BYTEREGS. Yes, unions can contain structures as they are a valid C language data type.

Each structure in the REGS union represents a different sized CPU register, 16-bit and 8-bit respectively. This layout reflects the architecture of the 8086 CPU found in the original IBM PC and its various copies or “clones.” The 8086 CPU registers are illustrated in Figure 1.

8086 CPU Architecture (limited)

Figure 1. Basic 8086 CPU architecture. The data registers are shown, along with SI and DI. What’s not shown are the IP (instruction pointer), SP (stack pointer), flags registers, and others.

For example, the AX register holds 16 bits. The lower part, only 8-bits, is register AL. The upper part is register AH. Together, they make register AX. From the REGS union, AX is the “word” register, WORDREGS; AL or AH is a “byte” register, BYTEREGS. The goal of the union is to represent both of these items, AL and AH, which fit into register AX that holds the same data. Hence, the union is an ideal structure to reference data stored in whichever register type is required.

Here are the structures associated with the REGS union:

struct WORDREGS {
    unsigned int ax,bx,cx,dx,si,di,cflag,flags;
};

struct BYTEREGS {
    unsigned char al,ah,bl,bh,cl,ch,dl,dh;
};

The REGS union was used with the int86() function, which initiated an 8086 interrupt, or a system call directly to the CPU. Here’s sample code:

2026_09_26-Lesson.c

/* Clear the screen via the BIOS */
#include <dos.h>

int main()
{
    union REGS in,out;

/* Read the current video mode,
   BIOS function 0x0f, set in register ah */
    in.h.ah = 0x0f;
    int86(0x10,&in,&out);    /* call the BIOS */

/* the CPU register values are now saved in the
   'out' union */

/* Call BIOS function 0x00 to clear the screen */
    /* BIOS function 0x00, set in register ah */
    in.h.ah = 0x00;

    /* copy the video mode number currently in
       the al register */
    in.h.al = out.h.al;

    /* make the call */
    int86(0x10,&in,&out);

/* the screen is cleared */

    return 0;
}

This code calls the system BIOS via 8086 interrupt, 0x10. The BIOS function is loaded into the ah register, which is represented in the C code via REGS union member in.h.al. The int86() function — unique to MS-DOS and prototyped in the dos.h header file — requires three arguments: the interrupt number, the address of the CPU registers to set before the call, and the address for CPU registers set after the call.

The first call to int86() obtains the video mode, which is saved in the al register after the call returns.

The second call to int86() clears the screen for the mode obtained from the first call.

The program has no output and it will not build on a modern computer. My point is to reminisce (of course), but also to show a practical example of how a union can work to represent multiple values referenced in different ways. But unions can also be abused, which I cover in next week’s Lesson.

Leave a Reply