Isso foi parcialmente baseado no design da CPU. Na década de 1980, a maioria dos computadores tinha uma arquitetura de 16 bits, então um ponteiro poderia acessar um espaço de endereço virtual de 2 16 (65536) bytes. Isto foi, naturalmente, considerado demasiado restritivo. Quando o segmento de código é separado dos outros, você pode ter 2 16 bytes no código (instruções) e 2 16 bytes de dados, o que é menos ruim.
O "espaço de dados" de um programa / processo (usando os termos no sentido mais amplo possível) pode incluir
- Dados inicializados, incluindo
- Strings (por exemplo,
printf("Hello, world\n");
), - Variáveis inicializadas (por exemplo,
int i = 42;
) e - Em alguns casos, constantes usadas no código (por exemplo,
y = x + 17;
),
- Strings (por exemplo,
- Variáveis não inicializadas
(por exemplo,
int j;
,char mydata[80];
em contextos estáticos / globais), - Memória alocada dinamicamente (
malloc
,new
) e - Stack (argumentos da função, salvar / restaurar informações e variáveis locais).
Eu posso estar deixando algo de fora, mas os registros não contam (eles são essencialmente um caso especial de variáveis não inicializadas) e a memória compartilhada está além do escopo desta discussão. Note que, do acima exposto, somente os dados inicializados precisam ser salvos no arquivo executável - espaço para variáveis não inicializadas e a pilha é automaticamente alocada pelo SO quando o programa é executado. Então, os compiladores precisavam acompanhar os dados inicializados e variáveis não inicializadas separadamente.
Outra ruga é que o processador Intel 8086 (o trisavô do Pentium) requeria que cada segmento fosse um grande bloco contíguo de memória. O kernel Unix consiste em um grande bloco de código e um grande bloco de dados (dados inicializados e variáveis não inicializadas), mas usa uma pilha separada para cada processo. Portanto, o 8086 exigia a habilidade ter a pilha em um segmento diferente de todos os outros dados.
Há muitas informações sobre isso na web. Por exemplo, na Wikipédia: