Estou escrevendo o código C para um sistema em que o endereço 0x0000 é válido e contém E / S de porta. Portanto, quaisquer possíveis bugs que acessem um ponteiro NULL permanecerão não detectados e ao mesmo tempo causarão um comportamento perigoso.
Por esse motivo, desejo redefinir NULL como outro endereço, por exemplo, um endereço que não é válido. Se eu acidentalmente acessar esse endereço, terei uma interrupção de hardware onde posso lidar com o erro. Acontece que tenho acesso a stddef.h para este compilador, então posso realmente alterar o cabeçalho padrão e redefinir NULL.
Minha pergunta é: isso entrará em conflito com o padrão C? Pelo que posso dizer a partir do 7.17 no padrão, a macro é definida pela implementação. Há algo em outro lugar no padrão afirmando que NULL deve ser 0?
Outro problema é que muitos compiladores executam inicialização estática definindo tudo como zero, não importa o tipo de dados. Mesmo que o padrão diga que o compilador deve definir inteiros para zero e ponteiros para NULL. Se eu redefinisse NULL para meu compilador, então sei que essa inicialização estática falhará. Eu poderia considerar isso um comportamento incorreto do compilador, embora eu ousadamente alterasse os cabeçalhos do compilador manualmente? Porque eu sei com certeza que este compilador específico não acessa a macro NULL ao fazer a inicialização estática.
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.if(ptr) { /* do something on ptr*/ }
? Funcionará se NULL for definido diferente de 0x0?Respostas:
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 umNULL
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, eNULL
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 deNULL
si no entanto, como entãoNULL
seria avaliada como verdadeira.Especificamente, você precisa:
-1
.0
para verificar o valor mágico (§6.5.9 / 6)Existem algumas coisas com as quais você não precisa lidar. Por exemplo:
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:Além disso:
x
não é garantido que seja0
.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:
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.
fonte
NULL
.O padrão afirma que uma expressão de constante inteira com valor 0, ou uma expressão convertida para o
void *
tipo, é uma constante de ponteiro nula. Isso significa que(void *)0
é sempre um ponteiro nulo, masint i = 0;
, dado ,(void *)i
não precisa ser.A implementação C consiste no compilador junto com seus cabeçalhos. Se você modificar os cabeçalhos para redefinir
NULL
, mas não modificar o compilador para corrigir inicializações estáticas, terá criado uma implementação não conforme. É toda a implementação em conjunto que apresenta comportamento incorreto e, se você a quebrou, não tem mais ninguém para culpar;)Você deve corrigir mais do que apenas inicializações estáticas, é claro - dado um ponteiro
p
,if (p)
é equivalente aif (p != NULL)
, devido à regra acima.fonte
Se você usar a biblioteca C std, terá problemas com funções que podem retornar NULL. Por exemplo, a documentação do malloc afirma:
Como malloc e as funções relacionadas já estão compiladas em binários com um valor NULL específico, se você redefinir NULL, não será capaz de usar diretamente a biblioteca C std, a menos que possa reconstruir sua cadeia de ferramentas inteira, incluindo as bibliotecas C std.
Além disso, devido ao uso de NULL pela biblioteca std, se você redefinir NULL antes de incluir cabeçalhos std, poderá sobrescrever uma definição NULL listada nos cabeçalhos. Qualquer coisa embutida seria inconsistente dos objetos compilados.
Em vez disso, eu definiria seu próprio NULL, "MYPRODUCT_NULL", para seus próprios usos e evitaria ou traduziria de / para a biblioteca C std.
fonte
Deixe NULL sozinho e trate o IO para a porta 0x0000 como um caso especial, talvez usando uma rotina escrita em assembler e, portanto, não sujeito à semântica C padrão. IOW, não redefina NULL, redefina a porta 0x00000.
Observe que se você está escrevendo ou modificando um compilador C, o trabalho necessário para evitar a desreferenciação de NULL (assumindo que no seu caso a CPU não está ajudando) é o mesmo, não importa como NULL seja definido, então é mais fácil deixar NULL definido como zero e certifique-se de que o zero nunca pode ser desreferenciado de C.
fonte
*p
,p[]
oup()
, portanto, o compilador só precisa se preocupar com aqueles para proteger a porta IO 0x0000.Considerando a extrema dificuldade em redefinir NULL conforme mencionado por outros, talvez seja mais fácil redefinir a desreferenciação para endereços de hardware bem conhecidos. Ao criar um endereço, adicione 1 a cada endereço conhecido, de modo que sua porta IO conhecida seja:
Se os endereços com os quais você está preocupado estiverem agrupados e você puder se sentir seguro de que adicionar 1 ao endereço não entrará em conflito com nada (o que não deveria ocorrer na maioria dos casos), você poderá fazer isso com segurança. E então você não precisa se preocupar em reconstruir sua cadeia de ferramentas / std lib e expressões no formato:
ainda funciona
Louco eu sei, mas pensei em lançar a ideia por aí :).
fonte
O padrão de bits para o ponteiro nulo pode não ser o mesmo que o padrão de bits para o inteiro 0. Mas a expansão da macro NULL deve ser uma constante de ponteiro nulo, que é um inteiro constante de valor 0 que pode ser convertido em (void *).
Para alcançar o resultado desejado sem perder a conformidade, você terá que modificar (ou talvez configurar) sua cadeia de ferramentas, mas é possível.
fonte
Você está procurando problemas. Redefinir
NULL
para um valor não nulo quebrará este código:fonte