This mode that’s present since the old days of the 8086 is still the mode processors are booting in. At least BIOS/EFI code runs in it and when booting in legacy mode also bootloaders are running in 16 bit mode.
Adresses are calculated as a pair of segment address and offset. A segment address is just a 16 bit value inside one of the segment registers (CS, DS, ES, FS, GS, SS) that simply gets multiplied by 16 - then the offset is added. So
physicalAddress = (segment << 4) + offset
This of course means that there is an ambiguity in addressing memory locations. The address 0000:0010 adresses the same location as 0001:0000. Note that each segment is exactly 65536 bytes long (i.e. 64 KByte). This also has been the limit for executables in old CP/M and MS-DOS .com file format.
The segments in use are:
The codesegment CS that’s used in conjunction with the instruction pointer IP to address the next instruction to be fetched and executed. Normally CS is just updated by either a far-call, far-jump, interrupt or interrupt return.
The datasegment DS that’s used when accessing memory without segment override except for some string functions that are using ES as destination and since the 80486 the additional segments FS and GS that might be used to access some memory locations using overrides.
The stack segment SS that’s used in conjunction with the stack pointer SP to provide a downwards growing stack.
There have been some differnt definition for memory models used by compilers in this mode:
tiny model used a single overlapping 64 KByte data and code area (i.e. CS equal to DS, ES, FS and GS)
small model used one data segment and one code segment (non overlapping).
compact model had a single code but multiple data segments
medium used multiple code and a single data segment
large model used multiple code and multiple data segments that are swapped as required.
huge was the large memory model but single data structures have been larger than a segment and crossed segment boundaries.
These memory models have been important for compiler developers. On current compilers most of the time it’s expected that code, data and especially stack segments overlap - on some machines code might be separate but stack and data is assumed to be flat. This is required to intermix local variables that are normally allocated on the stack and data on the heap. Since loading segment registers normally flushes some internal caches and takes longer than simply loading a general purpose register far pointer usage is also minimized by compilers whenever possible.
There are not many modern compilers that are capable of targeting real mode (which is especially a problem when it comes to development of firmware or bootloaders). To circumvent these problems solutions like DOS4GW already switches into protected mode. One major compilers that support 16 bit mode was the Watcom C compiler.
🖇️cf. C Language Fundamental p. 391. No.8