Em C (ou C ++, nesse caso), os ponteiros são especiais se tiverem o valor zero: sou aconselhado a definir ponteiros como zero após liberar sua memória, porque significa que liberar o ponteiro novamente não é perigoso; quando ligo para malloc, ele retorna um ponteiro com o valor zero, se não conseguir me recuperar a memória; Uso if (p != 0)
o tempo todo para garantir que os ponteiros passados sejam válidos etc.
Mas como o endereçamento de memória começa em 0, 0 não é tão válido quanto qualquer outro? Como 0 pode ser usado para manipular ponteiros nulos, se for esse o caso? Por que um número negativo não é nulo?
Editar:
Um monte de boas respostas. Resumirei o que foi dito nas respostas expressas à medida que minha própria mente o interpreta e espero que a comunidade me corrija se eu não entender.
Como tudo na programação, é uma abstração. Apenas uma constante, não muito relacionada ao endereço 0. C ++ 0x enfatiza isso adicionando a palavra-chave
nullptr
.Não é nem mesmo uma abstração de endereço, é a constante especificada pelo padrão C e o compilador pode convertê-lo em outro número, desde que garanta que nunca seja igual a um endereço "real" e seja igual a outros ponteiros nulos se 0 não for o melhor valor para usar na plataforma.
Caso não seja uma abstração, como era o caso nos primeiros dias, o endereço 0 é usado pelo sistema e fora dos limites para o programador.
Minha sugestão de número negativo foi um pouco de brainstorming, admito. Usar um número inteiro assinado para endereços é um pouco inútil se isso significa que, além do ponteiro nulo (-1 ou qualquer outro), o espaço de valor é dividido igualmente entre números inteiros positivos que produzem endereços válidos e números negativos que são desperdiçados.
Se qualquer número é sempre representável por um tipo de dados, é 0. (Provavelmente 1 também é. Penso no número inteiro de um bit que seria 0 ou 1 se não assinado, ou apenas no bit assinado se assinado ou no número inteiro de dois bits que seria [-2, 1], mas você pode simplesmente usar 0 como nulo e 1 como o único byte acessível na memória.)
Ainda há algo que não está resolvido em minha mente. A pergunta Stack Overflow Pointer para um endereço fixo específico me diz que, mesmo que 0 para ponteiro nulo seja uma abstração, outros valores de ponteiro não são necessariamente. Isso me leva a postar outra pergunta sobre estouro de pilha. Gostaria de acessar o endereço zero? .
if (p != 0)
paraif (p)
qual idioma comum em C e C ++, embora tenha que abandonar o hábito se adotar o Java.0xDEADBEEF
.Respostas:
2 pontos:
somente o valor constante 0 no código-fonte é o ponteiro nulo - a implementação do compilador pode usar o valor que desejar ou precisar no código em execução. Algumas plataformas têm um valor de ponteiro especial que é 'inválido' que a implementação pode usar como ponteiro nulo. O FAQ C tem uma pergunta: "Sério, alguma máquina real realmente usou ponteiros nulos diferentes de zero ou representações diferentes para ponteiros para tipos diferentes?" , que aponta várias plataformas que usaram essa propriedade 0 como ponteiro nulo na origem C, enquanto representadas de maneira diferente no tempo de execução. O padrão C ++ possui uma observação que esclarece que a conversão "de uma expressão constante integral com valor zero sempre gera um ponteiro nulo,
um valor negativo pode ser tão utilizável pela plataforma quanto um endereço - o padrão C simplesmente precisava escolher algo para indicar um ponteiro nulo e o zero foi escolhido. Sinceramente, não tenho certeza se outros valores sentinela foram considerados.
Os únicos requisitos para um ponteiro nulo são:
fonte
Historicamente, o espaço de endereço começando em 0 era sempre ROM, usado em alguns sistemas operacionais ou rotinas de manipulação de interrupções de baixo nível, atualmente, como tudo é virtual (incluindo espaço de endereço), o sistema operacional pode mapear qualquer alocação para qualquer endereço, para que possa especificamente NÃO aloque nada no endereço 0.
fonte
IIRC, não é garantido que o valor "ponteiro nulo" seja zero. O compilador converte 0 em qualquer valor "nulo" apropriado para o sistema (que na prática é provavelmente sempre zero, mas não necessariamente). A mesma conversão é aplicada sempre que você comparar um ponteiro com zero. Como você só pode comparar ponteiros entre si e contra esse valor especial-0, isola o programador de saber qualquer coisa sobre a representação de memória do sistema. Quanto ao motivo pelo qual eles escolheram 0 em vez de 42 ou algo assim, acho que é porque a maioria dos programadores começa a contar com 0 :) (Além disso, na maioria dos sistemas, 0 é o primeiro endereço de memória e eles queriam que fosse conveniente, pois em as traduções práticas que estou descrevendo raramente acontecem; o idioma apenas as permite).
fonte
int* p = 0
) pode criar um ponteiro contendo o valor0xdeadbeef
ou qualquer outro valor que ele prefira. 0 é um ponteiro nulo, mas um ponteiro nulo não é necessariamente um ponteiro para endereçar zero. :)Você deve estar entendendo mal o significado do zero constante no contexto do ponteiro.
Nem em C nem em ponteiros C ++ podem "ter valor zero". Ponteiros não são objetos aritméticos. Eles não podem ter valores numéricos como "zero" ou "negativo" ou qualquer coisa dessa natureza. Portanto, sua declaração sobre "ponteiros ... tem o valor zero" simplesmente não faz sentido.
No C & C ++, os ponteiros podem ter o valor de ponteiro nulo reservado . A representação real do valor do ponteiro nulo não tem nada a ver com "zeros". Pode ser absolutamente qualquer coisa apropriada para uma determinada plataforma. É verdade que na maioria das plataformas o valor de ponteiro nulo é representado fisicamente por um valor real de endereço zero. No entanto, se em algum endereço da plataforma 0 for realmente usado para algum propósito (ou seja, você pode precisar criar objetos no endereço 0), o valor do ponteiro nulo nessa plataforma provavelmente será diferente. Pode ser representado fisicamente como
0xFFFFFFFF
valor do endereço ou como0xBAADBAAD
valor do endereço, por exemplo.No entanto, independentemente de como o valor do ponteiro nulo é representado em uma determinada plataforma, no seu código você continuará a designar ponteiros nulos por constante
0
. Para atribuir um valor de ponteiro nulo a um determinado ponteiro, você continuará usando expressões comop = 0
. É responsabilidade do compilador realizar o que você deseja e convertê-lo na representação adequada do valor do ponteiro nulo, ou seja, traduzi-lo no código que colocará o valor do endereço0xFFFFFFFF
no ponteirop
, por exemplo.Em resumo, o fato de você usar
0
em seu código de sorce para gerar valores de ponteiro nulo não significa que o valor do ponteiro nulo esteja de alguma forma vinculado ao endereço0
. O0
que você usa no seu código-fonte é apenas "açúcar sintático" que não tem absolutamente nenhuma relação com o endereço físico real para o qual o valor do ponteiro nulo está "apontando".fonte
(p1 - nullptr) - (p2 - nullptr) == (p1 - p2)
.NULL
explicitamente não precisa ser representado por 0.Em alguns / muitos / todos os sistemas operacionais, o endereço de memória 0 é especial de alguma forma. Por exemplo, muitas vezes é mapeado para memória inválida / inexistente, o que causa uma exceção se você tentar acessá-la.
Eu acho que os valores dos ponteiros normalmente são tratados como números não assinados: caso contrário, por exemplo, um ponteiro de 32 bits seria capaz de endereçar apenas 2 GB de memória, em vez de 4 GB.
fonte
Meu palpite seria que o valor mágico 0 foi escolhido para definir um ponteiro inválido, pois poderia ser testado com menos instruções. Algumas linguagens de máquina definem automaticamente os sinalizadores de zero e de acordo com os dados ao carregar registros, para que você possa testar um ponteiro nulo com uma carga simples e instruções de ramificação sem executar uma instrução de comparação separada.
(A maioria dos ISAs apenas define sinalizadores nas instruções da ALU, mas não carrega. E, geralmente, você não está produzindo ponteiros via computação, exceto no compilador ao analisar a fonte C Mas, pelo menos, você não precisa de uma constante arbitrária de largura de ponteiro para comparar contra.)
No Commodore Pet, Vic20 e C64, que foram as primeiras máquinas em que trabalhei, a RAM foi iniciada no local 0, portanto, era totalmente válido ler e escrever usando um ponteiro nulo, se você realmente quisesse.
fonte
Eu acho que é apenas uma convenção. Deve haver algum valor para marcar um ponteiro inválido.
Você perde apenas um byte de espaço de endereço, que raramente deve ser um problema.
Não há indicadores negativos. Os ponteiros sempre estão sem sinal. Além disso, se eles pudessem ser negativos, sua convenção significaria que você perderia metade do espaço de endereço.
fonte
char *p = (char *)1; --p;
. Como o comportamento em um ponteiro nulo é indefinido pelo padrão, este sistema pode terp
realmente ler e escrever o endereço 0, incremento para dar o endereço1
, etc.char x = ((char*)0);
ler o endereço zero e armazenar esse valor em x. Esse código produziria Comportamento indefinido em qualquer implementação que não definisse seu comportamento, mas o fato de um padrão dizer que algo é Comportamento indefinido de forma alguma proíbe as implementações de oferecer suas próprias especificações para o que fará.*(char *)0
. Isso é verdade, mas, na minha sugestão, a implementação não precisa definir o comportamento de*(char *)0
ou de nenhuma outra operação de ponteiro nulo.char *p = (char*)1; --p;
apenas seria definido pelo padrão se essa sequência tivesse sido executada após um ponteiro para algo diferente do primeiro byte de um objeto ter sido convertido em umintptr_t
, e o resultado desse lançamento produzisse o valor 1 e, nesse caso específico, o resultado de--p
produziria um ponteiro para o byte anterior àquele cujo valor do ponteiro, quando convertido emintptr_t
, havia produzido1
.Embora C use 0 para representar o ponteiro nulo, lembre-se de que o valor do ponteiro em si pode não ser zero. No entanto, a maioria dos programadores sempre usará sistemas onde o ponteiro nulo é, de fato, 0.
Mas por que zero? Bem, é um endereço que todo sistema compartilha. E, muitas vezes, os endereços baixos são reservados para fins de sistema operacional, portanto, o valor funciona além de estar fora dos limites dos programas aplicativos. A atribuição acidental de um valor inteiro a um ponteiro tem a probabilidade de terminar zero como qualquer outra coisa.
fonte
Historicamente, a pouca memória de um aplicativo era ocupada por recursos do sistema. Foi nesses dias que zero se tornou o valor nulo padrão.
Embora isso não seja necessariamente verdade para os sistemas modernos, ainda é uma má idéia definir valores de ponteiro para qualquer coisa, exceto a alocação de memória que você forneceu.
fonte
Com relação ao argumento de não definir um ponteiro como nulo após excluí-lo, para que futuras exclusões "exponham erros" ...
Se você está realmente preocupado com isso, uma abordagem melhor, que certamente funcionará, é alavancar assert ():
Isso requer digitação extra e uma verificação extra durante a compilação de depuração, mas é certo que você terá o que deseja: observe quando o ptr é excluído 'duas vezes'. A alternativa dada na discussão do comentário, não definir o ponteiro como nulo para que você sofra uma falha, simplesmente não garante que seja bem-sucedida. Pior, ao contrário do que foi dito acima, pode causar uma falha (ou muito pior!) Em um usuário se um desses "bugs" chegar à prateleira. Finalmente, esta versão permite continuar executando o programa para ver o que realmente acontece.
Sei que isso não responde à pergunta, mas estava preocupado que alguém que lesse os comentários chegasse à conclusão de que é considerado "boa prática" NÃO definir indicadores para 0 se for possível que eles sejam enviados gratuitamente () ou apague duas vezes. Nos poucos casos em que é possível, NUNCA é uma boa prática usar o Undefined Behavior como uma ferramenta de depuração. Ninguém que já teve que caçar um bug que foi causado pela exclusão de um ponteiro inválido proporia isso. Esses tipos de erros levam horas para serem caçados e quase sempre afetam o programa de uma maneira totalmente inesperada, difícil de rastrear de volta ao problema original.
fonte
Uma razão importante pela qual muitos sistemas operacionais usam all-bits-zero para a representação de ponteiro nulo é que isso significa
memset(struct_with_pointers, 0, sizeof struct_with_pointers)
e similar definirá todos os ponteiros internosstruct_with_pointers
para ponteiros nulos. Isso não é garantido pelo padrão C, mas muitos programas assumem isso.fonte
Em uma das antigas máquinas DEC (PDP-8, eu acho), o tempo de execução C protegeria a memória da primeira página da memória, de forma que qualquer tentativa de acessar a memória naquele bloco faria com que uma exceção fosse levantada.
fonte
A escolha do valor sentinela é arbitrária e, de fato, isso está sendo abordado na próxima versão do C ++ (informalmente conhecida como "C ++ 0x", que provavelmente será conhecida no futuro como ISO C ++ 2011) com a introdução do palavra-chave
nullptr
- para representar um ponteiro com valor nulo. Em C ++, um valor 0 pode ser usado como expressão de inicialização para qualquer POD e para qualquer objeto com um construtor padrão, e tem o significado especial de atribuir o valor sentinela no caso de uma inicialização de ponteiro. Quanto ao motivo pelo qual um valor negativo não foi escolhido, os endereços geralmente variam de 0 a 2 N-1 para algum valor N. Em outras palavras, os endereços geralmente são tratados como valores não assinados. Se o valor máximo fosse usado como o valor sentinela, ele teria que variar de sistema para sistema, dependendo do tamanho da memória, enquanto 0 é sempre um endereço representável. Também é usado por razões históricas, como o endereço de memória 0 era normalmente inutilizável em programas e atualmente a maioria dos sistemas operacionais possui partes do kernel carregadas nas páginas inferiores da memória, e essas páginas são tipicamente protegidas de maneira que, se tocado (desreferenciado) por um programa (salve o kernel) causará uma falha.fonte
Tem que ter algum valor. Obviamente, você não deseja pisar nos valores que o usuário pode legitimamente querer usar. Eu especularia que, como o tempo de execução C fornece o segmento BSS para dados inicializados com zero, faz certo sentido interpretar zero como um valor de ponteiro não inicializado.
fonte
Raramente um sistema operacional permite que você escreva no endereço 0. É comum colocar coisas específicas do sistema operacional em pouca memória; ou seja, IDTs, tabelas de páginas, etc. (As tabelas precisam estar na RAM e é mais fácil colocá-las na parte inferior do que tentar determinar onde fica a parte superior da RAM.) E nenhum sistema operacional no seu perfeito juízo permitirá edite as tabelas do sistema, quer ou não.
Isso pode não estar na cabeça de K&R quando criaram C, mas (junto com o fato de que 0 == nulo é bem fácil de lembrar) faz de 0 uma escolha popular.
fonte
O valor
0
é um valor especial que assume vários significados em expressões específicas. No caso de ponteiros, como foi apontado muitas vezes, é usado provavelmente porque na época era a maneira mais conveniente de dizer "insira o valor padrão da sentinela aqui". Como expressão constante, ele não tem o mesmo significado que zero bit a bit (ou seja, todos os bits configurados como zero) no contexto de uma expressão de ponteiro. No C ++, existem vários tipos que não têm uma representação zero bit a bitNULL
, como membro do ponteiro e ponteiro para a função de membro.Felizmente, C ++ 0x tem uma nova palavra-chave para "expressão que significa um ponteiro inválido conhecido que também não mapear para bit a bit zero para expressões integrais":
nullptr
. Embora existam alguns sistemas que você pode direcionar com o C ++ que permitem a desreferenciação do endereço 0 sem vomitar, então tenha cuidado com o programador.fonte
Já existem muitas boas respostas neste tópico; provavelmente existem muitas razões diferentes para preferir o valor
0
para ponteiros nulos, mas vou adicionar mais duas:fonte
Isso depende da implementação de ponteiros em C / C ++. Não há nenhuma razão específica para que NULL seja equivalente em atribuições a um ponteiro.
fonte
Existem razões históricas para isso, mas também existem razões de otimização para isso.
É comum que o sistema operacional forneça um processo com páginas de memória inicializadas como 0. Se um programa deseja interpretar parte dessa página de memória como um ponteiro, então é 0, então é fácil o suficiente para o programa determinar se esse ponteiro é não inicializado. (isso não funciona tão bem quando aplicado a páginas flash não inicializadas)
Outro motivo é que, em muitos processadores, é muito fácil testar a equivalência de um valor a 0. Às vezes é uma comparação gratuita feita sem nenhuma instrução extra necessária e geralmente pode ser feita sem a necessidade de fornecer um valor zero em outro registro ou como um literal no fluxo de instruções para comparar.
As comparações baratas para a maioria dos processadores são assinadas com menos de 0 e igual a 0. (assinadas com mais de 0 e diferente de 0 são implícitas por ambos)
Como 1 valor de todos os valores possíveis precisa ser reservado como ruim ou não inicializado, é possível que ele seja o que tem o teste mais barato para equivalência ao valor ruim. Isso também se aplica a cadeias de caracteres terminadas '\ 0'.
Se você tentasse usar maior ou menor que 0 para esse fim, acabaria dividindo seu intervalo de endereços pela metade.
fonte
A constante
0
é utilizada em vez deNULL
porque C foi feita por alguns trilhões homens das cavernas de anos,NULL
,NIL
,ZIP
, ouNADDA
teria tudo feito muito mais sentido do que0
.De fato. Embora muitos sistemas operacionais o proíbam de mapear qualquer coisa no endereço zero, mesmo em um espaço de endereço virtual (as pessoas perceberam que C é uma linguagem insegura e, refletindo que os erros de desreferência de ponteiro nulo são muito comuns, decidiram "corrigi-los" desautorizando o código do espaço do usuário para mapear para a página 0; portanto, se você chamar um retorno de chamada, mas o ponteiro de retorno de chamada for NULL, você não acabará executando algum código arbitrário).
Porque
0
usado em comparação com um ponteiro será substituído por algum valor específico da implementação , que é o valor de retorno do malloc em uma falha do malloc.Isso seria ainda mais confuso.
fonte
int
não era apenas do mesmo tamanho que um ponteiro - em muitos contextos, umint
e um ponteiro podiam ser usados de forma intercambiável. Se uma rotina esperasse um ponteiro e um passasse em um número inteiro 57, a rotina usaria o endereço com o mesmo padrão de bits que o número 57. Nessas máquinas específicas, o padrão de bits para indicar um ponteiro nulo era 0, passando um int 0 passaria um ponteiro nulo.( Leia este parágrafo antes de ler a publicação.Estou perguntando a qualquer pessoa interessada em ler este post que tente ler com atenção e, é claro, não diminua o voto até que você o entenda completamente, obrigado. )
Agora é um wiki da comunidade, portanto, se alguém discordar de algum dos conceitos, modifique-o, com uma explicação clara e detalhada do que está errado e por que, e se possível, cite fontes ou forneça provas que possam ser reproduzidas.
Responda
Aqui estão alguns outros motivos que podem ser os fatores subjacentes a NULL == 0
if(!my_ptr)
vez deif(my_ptr==NULL)
.Aqui eu gostaria de dizer uma palavra sobre outras respostas
Não por causa do açúcar sintático
Dizer que NULL é zero por causa do açúcar sintático, não faz muito sentido; se sim, por que não usar o índice 0 de uma matriz para manter seu comprimento?
De fato, C é a linguagem que mais se assemelha à implementação interna, faz sentido dizer que C escolheu zero apenas por causa do açúcar sintático? Eles preferem fornecer uma palavra-chave nula (como muitos outros idiomas) em vez de mapear zero para NULL!
Assim, a partir de hoje, pode ser apenas açúcar sintático, é claro que a intenção original dos desenvolvedores da linguagem C não era o açúcar sintático, como mostrarei mais adiante.
1) a especificação
No entanto, embora seja verdade que a especificação C fala da constante 0 como o ponteiro nulo (seção 6.3.2.3) e também define NULL a ser definido como implementação (seção 7.19 na especificação C11 e 7.17 na especificação C99), o permanece o fato de que, no livro "The C Programming Language", escrito pelos inventores de C, é indicado o seguinte na seção 5.4:
Como se pode ver (pelas palavras "endereço zero") pelo menos a intenção original dos autores de C era o endereço zero, e não o zero constante, além disso, a partir deste trecho, parece que a razão pela qual a especificação fala do o zero constante provavelmente não deve excluir uma expressão avaliada como zero, mas incluir a constante inteira zero como a única constante inteira permitida para uso em um contexto de ponteiro sem conversão.
2) Resumo
Embora a especificação não diga explicitamente que um endereço zero pode ser tratado diferente da constante zero, ele não diz isso, e o fato de, ao lidar com a constante ponteiro nulo , não afirmar que sua implementação é definida como pela constante definida NULL , em vez disso, afirma que é zero, mostra que pode haver uma diferença entre a constante zero e o endereço zero.
(No entanto, se esse for o caso, eu me pergunto por que NULL é a implementação definida, pois, nesse caso, NULL também pode ser o zero constante, pois o compilador precisa converter todas as constantes de zero para a implementação real definida como NULL?)
No entanto, não vejo isso em ação real e, nas plataformas gerais, o endereço zero e o zero constante são tratados da mesma forma e lançam a mesma mensagem de erro.
Além disso, o fato é que os sistemas operacionais de hoje estão reservando a primeira página inteira (intervalo de 0x0000 a 0xFFFF), apenas para impedir o acesso ao endereço zero devido ao ponteiro NULL de C (consulte http://en.wikipedia.org/wiki/ Zero_page , bem como "Windows Via C / C ++ por Jeffrey Richter e Christophe Nasarre (publicado pela Microsoft Press)").
Assim, eu pediria a qualquer pessoa que afirmasse que ele realmente foi visto em ação, para especificar a plataforma, o compilador e o código exato que ele realmente criou (embora devido à definição vaga na especificação [como eu mostrei]) qualquer compilador e a plataforma é livre para fazer o que ele quiser).
No entanto, aparentemente, parece que os autores de C não tinham isso em mente, e estavam falando do "endereço zero", e que "C garante que nunca seja um endereço válido", assim como "NULL é apenas um mnemônico ", mostrando claramente que sua intenção original não era o" açúcar sintático ".
Não por causa do sistema operacional
Alegando também que o sistema operacional nega acesso ao endereço zero, por alguns motivos:
1) Quando C foi escrito, não havia essa restrição, como se pode ver nesta página da wiki http://en.wikipedia.org/wiki/Zero_page .
2) O fato é que os compiladores C acessaram o endereço de memória zero.
Este parece ser o fato do artigo a seguir da BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )
(De fato, a partir de hoje (como citei as referências acima da wikipedia e da microsoft press), a razão para restringir o acesso ao endereço zero é por causa dos ponteiros NULL de C! Portanto, no final, acontece o contrário!)
3) Lembre-se de que C também é usado para escrever sistemas operacionais e até compiladores C!
De fato, o C foi desenvolvido com a finalidade de escrever o sistema operacional UNIX com ele e, como tal, parece não haver razão para que eles se restrinjam do endereço zero.
(Hardware) Explicação sobre como os computadores são (fisicamente) capazes de acessar o endereço zero
Há outro ponto que quero explicar aqui, como é possível fazer referência ao endereço zero?
Pense nisso por um segundo, os endereços são buscados pelo processador e, em seguida, enviados como voltagens no barramento de memória, que é usado pelo sistema de memória para chegar ao endereço real e, ainda assim, um endereço zero significa que não há voltagem. , então como o hardware físico do sistema de memória está acessando o endereço zero?
A resposta parece ser: esse endereço zero é o padrão e, em outras palavras, o endereço zero é sempre acessível pelo sistema de memória quando o barramento de memória está completamente desligado e, como tal, qualquer solicitação de leitura ou gravação sem especificar um endereço real (que é o caso do endereço zero) acessa automaticamente o endereço zero.
fonte