This was based partly on CPU design.
Into the 1980s, most computers had a 16-bit architecture,
so a pointer could access a virtual address space
of 216 (65536) bytes.
This was, naturally, considered to be too restrictive.
When the code segment is separated from the others,
you can have 216 bytes worth of code (instructions)
and 216 bytes worth of data, which is less bad.
The “data space” of a program/process
(using the terms in the broadest possible sense) can include
- Initialized data, including
- Strings (e.g.,
printf("Hello, world\n");
),
- Initialized variables (e.g.,
int i = 42;
), and
- In some cases, constants used in code (e.g.,
y = x + 17;
),
- Non-initialized variables
(e.g.,
int j;
, char mydata[80];
in static/global contexts),
- Dynamically allocated memory (
malloc
, new
), and
- Stack (function arguments, save/restore information, and local variables).
I may be leaving something out, but registers don’t really count
(they are essentially a special case of non-initialized variables)
and shared memory is beyond the scope of this discussion.
Note that, of the above,
only the initialized data need to be saved in the executable file —
space for non-initialized variables and
the stack is automatically allocated by the OS when the program is executed.
So the compilers needed to keep track of initialized data
and non-initialized variables separately.
Another wrinkle is that the Intel 8086 CPU
(the great-great-grandfather of the Pentium)
required each segment to be one big, contiguous block of memory.
The Unix kernel consists of one big block of code and one big block of data
(initialized data and non-initialized variables),
but it uses a separate stack for each process.
Therefore, the 8086 required the ability
to have the stack in a different segment from all the other data.
There’s lots of information about this out there on the web.
For example, in Wikipedia: