O padrão C não requer que ponteiros nulos estejam no endereço zero da máquina. NO ENTANTO, lançar uma 0
constante para um valor de ponteiro deve resultar em um NULL
ponteiro (§6.3.2.3 / 3), e avaliar o ponteiro nulo como um booleano deve ser falso. Isso pode ser um pouco estranho se você realmente não quer um endereço zero, e NULL
não é o endereço zero.
No entanto, com modificações (pesadas) no compilador e na biblioteca padrão, não é impossível NULL
ser representado com um padrão de bits alternativo enquanto permanece estritamente em conformidade com a biblioteca padrão. É não suficiente para simplesmente mudar a definição de NULL
si no entanto, como então NULL
seria avaliada como verdadeira.
Especificamente, você precisa:
- Faça com que zeros literais em atribuições a ponteiros (ou conversões em ponteiros) sejam convertidos em algum outro valor mágico, como
-1
.
- Faça testes de igualdade entre ponteiros e um número inteiro constante
0
para verificar o valor mágico (§6.5.9 / 6)
- Organize todos os contextos nos quais um tipo de ponteiro é avaliado como booleano para verificar a igualdade do valor mágico em vez de verificar o zero. Isso decorre da semântica do teste de igualdade, mas o compilador pode implementá-lo de forma diferente internamente. Consulte §6.5.13 / 3, §6.5.14 / 3, §6.5.15 / 4, §6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4
- Como caf apontou, atualize a semântica para inicialização de objetos estáticos (§6.7.8 / 10) e inicializadores compostos parciais (§6.7.8 / 21) para refletir a nova representação de ponteiro nulo.
- Crie uma maneira alternativa de acessar o endereço zero verdadeiro.
Existem algumas coisas com as quais você não precisa lidar. Por exemplo:
int x = 0;
void *p = (void*)x;
Depois disso, p
NÃO é garantido que seja um ponteiro nulo. Apenas atribuições constantes precisam ser tratadas (esta é uma boa abordagem para acessar o endereço zero verdadeiro). Da mesma forma:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Além disso:
void *p = NULL;
int x = (int)p;
x
não é garantido que seja 0
.
Em suma, essa mesma condição foi aparentemente considerada pelo comitê de linguagem C e considerações feitas para aqueles que escolheriam uma representação alternativa para NULL. Tudo o que você precisa fazer agora é fazer grandes alterações em seu compilador e pronto, pronto :)
Como uma observação lateral, pode ser possível implementar essas mudanças com um estágio de transformação do código-fonte antes do compilador apropriado. Ou seja, em vez do fluxo normal do pré-processador -> compilador -> montador -> vinculador, você adicionaria um pré-processador -> transformação NULL -> compilador -> montador -> vinculador. Então você pode fazer transformações como:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Isso exigiria um analisador C completo, bem como um analisador de tipo e análise de typedefs e declarações de variáveis para determinar quais identificadores correspondem a ponteiros. No entanto, ao fazer isso, você pode evitar ter que fazer alterações nas partes de geração de código do compilador adequado. O clang pode ser útil para implementar isso - entendo que foi projetado com transformações como essa em mente. Você provavelmente ainda precisará fazer alterações na biblioteca padrão também, é claro.
mprotect
proteger. Ou, se a plataforma não tiver ASLR ou similar, endereços além da memória física da plataforma. Boa sorte.