Este pedaço de código é válido (e comportamento definido)?
int &nullReference = *(int*)0;
Ambos g ++ e clang ++ compilação-lo sem qualquer aviso, mesmo quando se usa -Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
...
Claro que a referência não é realmente nula, uma vez que não pode ser acessada (significaria desreferenciar um ponteiro nulo), mas poderíamos verificar se é nulo ou não verificando seu endereço:
if( & nullReference == 0 ) // null reference
c++
reference
null
language-lawyer
Peoro
fonte
fonte
if (false)
, eliminando a verificação, precisamente porque as referências não podem ser nulas de qualquer maneira. Uma versão melhor documentada existia no kernel Linux, onde uma verificação NULL muito semelhante foi otimizada: isc.sans.edu/diary.html?storyid=6820Respostas:
As referências não são indicadores.
8.3.2 / 1:
1.9 / 4:
Como Johannes diz em uma resposta excluída, há algumas dúvidas se "desreferenciar um ponteiro nulo" deve ser categoricamente declarado como comportamento indefinido. Mas este não é um dos casos que levantam dúvidas, uma vez que um ponteiro nulo certamente não aponta para um "objeto ou função válida", e não há desejo dentro do comitê de padrões de introduzir referências nulas.
fonte
&*p
comop
universal, isso não exclui o comportamento indefinido (que por sua natureza pode "parecer funcionar"); e eu discordo que umatypeid
expressão que busca determinar o tipo de um "ponteiro nulo desreferenciado" realmente desreferencia o ponteiro nulo. Já vi pessoas argumentarem seriamente que&a[size_of_array]
não se pode e não se deve confiar e, de qualquer forma, é mais fácil e seguro apenas escrevera + size_of_array
.*p
se refere, quandop
é um ponteiro nulo. C ++ atualmente não tem a noção de um lvalue vazio, que a edição 232 queria introduzir.typeid
trabalhos baseados na sintaxe, em vez de baseados na semântica. Ou seja, se você faztypeid(0, *(ostream*)0)
você faz tem um comportamento indefinido - nãobad_typeid
é garantido para ser jogado, mesmo que você passar um lvalue resultante de um ponteiro dereference nulo semanticamente. Mas sintaticamente no nível superior, não é uma desreferência, mas uma expressão de operador vírgula.A resposta depende do seu ponto de vista:
Se você julgar pelo padrão C ++, não poderá obter uma referência nula porque obtém o comportamento indefinido primeiro. Depois dessa primeira incidência de comportamento indefinido, o padrão permite que tudo aconteça. Então, se você escreve
*(int*)0
, você já tem um comportamento indefinido, do ponto de vista do padrão da linguagem, desreferenciando um ponteiro nulo. O resto do programa é irrelevante, uma vez que esta expressão seja executada, você está fora do jogo.No entanto, na prática, as referências nulas podem ser facilmente criadas a partir de ponteiros nulos e você não notará até que realmente tente acessar o valor por trás da referência nula. Seu exemplo pode ser um pouco simples demais, pois qualquer bom compilador de otimização verá o comportamento indefinido e simplesmente otimizará qualquer coisa que dependa dele (a referência nula nem será criada, ela será otimizada).
No entanto, essa otimização depende do compilador para provar o comportamento indefinido, o que pode não ser possível fazer. Considere esta função simples dentro de um arquivo
converter.cpp
:Quando o compilador vê essa função, ele não sabe se o ponteiro é um ponteiro nulo ou não. Portanto, ele apenas gera código que transforma qualquer ponteiro na referência correspondente. (Btw: Este é um noop, pois ponteiros e referências são exatamente a mesma besta no assembler.) Agora, se você tiver outro arquivo
user.cpp
com o códigoo compilador não sabe que
toReference()
cancelará a referência ao ponteiro passado e presumirá que ele retornará uma referência válida, que na prática será uma referência nula. A chamada é bem-sucedida, mas quando você tenta usar a referência, o programa falha. Esperançosamente. O padrão permite que qualquer coisa aconteça, incluindo o aparecimento de elefantes rosa.Você pode perguntar por que isso é relevante, afinal, o comportamento indefinido já foi acionado internamente
toReference()
. A resposta é depuração: referências nulas podem se propagar e proliferar da mesma forma que os ponteiros nulos fazem. Se você não está ciente de que podem existir referências nulas e aprende a evitar criá-las, você pode passar algum tempo tentando descobrir por que sua função de membro parece travar quando está apenas tentando ler umint
membro antigo comum (resposta: a instância na chamada do membro havia uma referência nula, entãothis
é um ponteiro nulo, e seu membro é calculado para estar localizado como endereço 8).Então, que tal verificar referências nulas? Você deu a linha
em sua pergunta. Bem, isso não funcionará: de acordo com o padrão, você tem um comportamento indefinido se desreferenciar um ponteiro nulo e não pode criar uma referência nula sem desreferenciar um ponteiro nulo, portanto, as referências nulas existem apenas dentro do domínio do comportamento indefinido. Visto que seu compilador pode assumir que você não está disparando um comportamento indefinido, ele pode assumir que não existe uma referência nula (embora ele emita prontamente um código que gera referências nulas!). Como tal, ele vê a
if()
condição, conclui que não pode ser verdade e apenas joga fora aif()
afirmação inteira . Com a introdução de otimizações de tempo de link, tornou-se totalmente impossível verificar referências nulas de uma maneira robusta.TL; DR:
Referências nulas são um tanto quanto uma existência horrível:
A existência deles parece impossível (= pelo padrão),
mas eles existem (= pelo código de máquina gerado),
mas você não pode vê-los se eles existirem (= suas tentativas serão otimizadas),
mas eles podem matá-lo sem saber de qualquer maneira (= seu programa trava em pontos estranhos, ou pior).
Sua única esperança é que eles não existam (= escreva seu programa para não criá-los).
Espero que isso não venha a assombrá-lo!
fonte
Se sua intenção era encontrar uma maneira de representar null em uma enumeração de objetos singleton, então é uma má ideia (des) referenciar null (é C ++ 11, nullptr).
Por que não declarar o objeto singleton estático que representa NULL dentro da classe como segue e adicionar um operador cast-to-pointer que retorna nullptr?
Editar: vários tipos de erros corrigidos e adição de instrução if em main () para testar o funcionamento do operador cast-to-pointer (que esqueci de ... meu erro) - 10 de março de 2015 -
fonte
o clang ++ 3.5 ainda avisa sobre isso:
fonte