Por que o endereço zero é usado para o ponteiro nulo?


121

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-chavenullptr .

  • 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? .


11
Você poderia facilmente mudar if (p != 0)para if (p)qual idioma comum em C e C ++, embora tenha que abandonar o hábito se adotar o Java.
Paul Tomblin

14
Excluir algo duas vezes significa que seu código está errado. Aconselho contra a configuração de ponteiros para nulo depois, para que você possa travar e corrigir o problema, não o suprima. De qualquer forma, você comete o erro de assumir que um endereço é um número inteiro. Isso não é necessariamente verdade e 0 representa apenas algum valor real do ponteiro, específico da implementação. Um "endereço negativo" não faz sentido, conceitualmente.
GManNickG

6
@ GMan: Talvez até seja uma boa idéia definir o ponteiro para um endereço que force uma falha, como 0xDEADBEEF.
Billy ONeal

5
A pergunta que nunca vai morrer!

8
@Noah: O ponto está definido como nulo -> ocultar erros de programação, não definido como nulo -> encontrar erros de programação. Não sei você, mas gostaria de ter meu código correto.
GManNickG

Respostas:


65

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:

  • é garantido comparar desigual a um ponteiro para um objeto real
  • quaisquer dois ponteiros nulos serão comparados com igual (o C ++ refina isso de forma que isso só precise reter ponteiros do mesmo tipo)

12
+1 Suspeito que 0 foi escolhido apenas por razões históricas. (0 sendo um endereço inicial e inválido, na maioria das vezes.) É claro que em geral essa suposição nem sempre é verdadeira, mas 0 funciona muito bem.
GManNickG

8
O espaço também pode ter sido um fator contribuinte. Nos dias em que o C foi desenvolvido, a memória era MUITO mais cara do que agora. O número zero pode ser convenientemente calculado usando uma instrução XOR ou sem a necessidade de carregar um valor imediato. Dependendo da arquitetura, isso pode economizar espaço.
Sparky

6
@ GMan - Você está correto. Nas primeiras CPUs, o endereço de memória zero era especial e tinha proteção de hardware contra o acesso da execução de software (em alguns casos, era o início do vetor de redefinição e sua modificação poderia impedir a reinicialização ou inicialização da CPU). Os programadores usavam essa proteção de hardware como uma forma de detecção de erro em seu software, permitindo que a lógica de decodificação de endereço da CPU verifique se há indicadores não inicializados ou inválidos, em vez de ter que gastar instruções da CPU para fazer isso. A convenção permanece até hoje, mesmo que o objetivo do endereço zero possa ter mudado.
BTA

10
O compilador Minix de 16 bits usou 0xFFFF para NULL.
Joshua

3
Em muitos sistemas embarcados, 0 é um endereço válido. O valor -1 (todos os bits um) também é um endereço válido. As somas de verificação para ROMs são difíceis de calcular quando os dados começam no endereço 0. :-(
Thomas Matthews

31

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.


6
É isso mesmo. É por convenção histórica, e os primeiros endereços foram usados ​​para manipuladores de interrupção, portanto são inutilizáveis ​​para programas normais. Além disso, 0 é "vazio", que pode ser interpretado como sem valor / sem ponteiro.
TomTom

15

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).


5
@ Justin: Você entendeu mal. A constante 0 é sempre o ponteiro nulo. O que o @meador está dizendo é que é possível que o ponteiro nulo (indicado pela constante 0) não corresponda ao endereço zero. Em algumas plataformas, a criação de um ponteiro nulo ( int* p = 0) pode criar um ponteiro contendo o valor 0xdeadbeefou qualquer outro valor que ele prefira. 0 é um ponteiro nulo, mas um ponteiro nulo não é necessariamente um ponteiro para endereçar zero. :)
jalf

Um ponteiro NULL é um valor reservado e, dependendo do compilador, pode haver qualquer padrão de bits. Ponteiro NULL não significa que ele aponta para o endereço 0.
Sharjeel Aziz

3
Mas @ Dalf, a constante 0 nem sempre é o ponteiro nulo. É o que escrevemos quando queremos que o compilador preencha o ponteiro nulo real da plataforma para nós. Em termos práticos, o ponteiro nulo normalmente faz corresponder ao endereço zero, embora, e eu interpretar a pergunta de Joel como perguntar por que isso acontece. Afinal, existe um byte de memória válido nesse endereço, então por que não usar um endereço inexistente de um byte inexistente em vez de remover um byte válido da reprodução? (Eu estou escrevendo o que eu imagino Joel estava pensando, não uma pergunta que eu estou me perguntando.)
Rob Kennedy

@ Rob: Mais ou menos. Eu sei o que você quer dizer e você está correto, mas eu também. :) O número inteiro constante 0 representa o ponteiro nulo no nível do código-fonte. Comparar um ponteiro nulo com 0 produz true. Atribuir 0 a um ponteiro define esse ponteiro como nulo. 0 é o ponteiro nulo. Mas a representação real na memória de um ponteiro nulo pode ser diferente do padrão de zero bits. (De qualquer forma, meu comentário foi em resposta a @ comentário agora excluídos do Justin, não para de @ Joel pergunta :).
jalf

@jalf @Rob Você precisa de alguns termos para esclarecer, eu acho. :) De §4.10 / 1: "Uma constante de ponteiro nulo é uma expressão de constante constante rvalue do tipo inteiro que é avaliado como zero. Uma constante de ponteiro nulo pode ser convertida em um tipo de ponteiro; o resultado é o valor de ponteiro nulo desse tipo e é distinguível de qualquer outro valor de ponteiro para objeto ou ponteiro para tipo de função ".
GManNickG

15

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 0xFFFFFFFFvalor do endereço ou como 0xBAADBAADvalor 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 como p = 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ço 0xFFFFFFFFno ponteiro p, por exemplo.

Em resumo, o fato de você usar 0em 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ço 0. O 0que 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".


3
<quote> Ponteiros não são objetos aritméticos </quote> A aritmética dos ponteiros é bastante bem definida em C e C ++. Parte do requisito é que ambos os ponteiros apontem para o mesmo composto. O ponteiro nulo não aponta para nenhum composto, portanto, usá-lo em expressões aritméticas de ponteiro é ilegal. Por exemplo, não é garantido isso (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt

5
Ben Voigt: A especificação da linguagem define a noção de tipo aritmético . Tudo o que estou dizendo é que os tipos de ponteiros não pertencem à categoria de tipos aritméticos. A aritmética dos ponteiros é uma história diferente e completamente independente, uma mera coincidência linguística.
AnT

1
Como alguém que lê objetos aritméticos deveria saber que significa "no sentido de tipos aritméticos" e não "no sentido de operadores aritméticos" (muitos dos quais são utilizáveis ​​em ponteiros) ou "no sentido de aritmética de ponteiros". No que diz respeito às coincidências linguísticas, o objeto aritmético tem mais letras em comum com a aritmética do ponteiro do que os tipos aritméticos . Ao mesmo tempo, o padrão fala sobre o valor do ponteiro . O pôster original provavelmente significava representação inteira de um ponteiro em vez de valor do ponteiro , e NULLexplicitamente não precisa ser representado por 0.
Ben Voigt

Bem, por exemplo, o termo objetos escalares na terminologia C / C ++ é apenas uma abreviação para objetos de tipos escalares (assim como objetos POD = objetos de tipos POD ). Eu usei o termo objetos aritméticos exatamente da mesma maneira, significando objetos de tipos aritméticos . Eu espero que "alguém" entenda dessa maneira. Alguém que nem sempre pode pedir esclarecimentos.
AnT

1
Eu trabalhei em um sistema onde (tanto quanto o hardware estava em causa) nula foi 0xffffffff e 0 foi um endereço perfeitamente válido
PM100

8

Mas como o endereçamento de memória começa em 0, 0 não é tão válido quanto qualquer outro?

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.

Por que um número negativo não é nulo?

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.


4
Eu codifiquei em um dispositivo em que o endereço zero era um endereço válido e não havia proteção de memória. Ponteiros nulos também eram todos-bits-zero; se você acidentalmente gravou em um ponteiro nulo, detonou as configurações do sistema operacional que estavam no endereço zero; hilaridade geralmente não acontecia.
MM

1
Sim: em uma CPU x86 de modo não protegido, por exemplo, o endereço 0 é a tabela de vetores de interrupção .
ChrisW

@ Chris: No x86 no modo não protegido, o endereço zero, em particular, é o vetor de interrupção dividido por zero, que alguns programas podem ter razões inteiramente legítimas para escrever.
Supercat2

Mesmo em plataformas nas quais o armazenamento utilizável começaria no endereço físico, zero, uma implementação C poderia facilmente usar o endereço zero para manter um objeto cujo endereço nunca é obtido, ou simplesmente deixar a primeira palavra de memória sem uso. Na maioria das plataformas, comparar com zero salva uma instrução versus comparar com qualquer outra coisa, portanto, desperdiçar a primeira palavra de armazenamento seria mais barato do que usar um endereço diferente de zero para nulo. Note que não há nenhuma exigência de que os endereços de coisas não abrangidos pelo C padrão (portos, por exemplo E / S ou vetores de interrupção) comparar desigual como nulo, nem que ...
supercat

... o processo do sistema ponteiro nulo acessa de maneira diferente de qualquer outro, portanto, all-bits-zero geralmente é um endereço adequado para "null", mesmo em sistemas onde o acesso ao local físico zero seria útil e significativo.
Supercat #

5

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.


3

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.


Nota: você realmente não perde espaço de endereço; você pode obter um ponteiro para o endereço 0, fazendo: 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ço 1, etc.
MM

@MattMcNabb: uma implementação em que o endereço zero é um endereço de hardware válido pode perfeitamente definir legitimamente o comportamento de 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á.
Supercat 24/03

@supercat ITYM *(char *)0. Isso é verdade, mas, na minha sugestão, a implementação não precisa definir o comportamento de *(char *)0ou de nenhuma outra operação de ponteiro nulo.
MM

1
@MattMcNabb: o comportamento de 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 um intptr_t, e o resultado desse lançamento produzisse o valor 1 e, nesse caso específico, o resultado de --pproduziria um ponteiro para o byte anterior àquele cujo valor do ponteiro, quando convertido em intptr_t, havia produzido 1.
Supercat 24/03

3

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.


3
A razão mais provável por trás de tudo isso é a seguinte: é barato distribuir memória pré-inicializada para zero e conveniente que os valores nessa memória representem algo significativo como o número inteiro 0, o ponto flutuante 0.0 e os ponteiros nulos. Os dados estáticos em C inicializados em zero / nulo não precisam ocupar espaço no executável e são mapeados para um bloco preenchido com zero quando carregados. O zero também pode receber tratamento especial em linguagens de máquina: comparações fáceis de zero, como "desvio se for igual a zero", etc. O MIPS ainda possui um registro fictício que é apenas uma constante zero.
Kaz

2

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.


2

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 ():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

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.


2

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 internos struct_with_pointerspara ponteiros nulos. Isso não é garantido pelo padrão C, mas muitos programas assumem isso.


1

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.


O PDP-8 não tinha compilador C. O PDP-11 não tinha proteção de memória e o VAX era famoso por retornar silenciosamente de 0 a dereferências de ponteiro NULL. Não tenho certeza a que máquina isso se refere.
fuz

1

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-chavenullptr - 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.


1

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.


0

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.


Isso não é verdade em modo protegido, e de fato, em determinadas configurações Linux, você pode escrever para o endereço virtual 0.
L̳o̳̳n̳̳g̳̳p̳o̳̳k̳̳e̳̳

0

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 bit NULL, 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.


0

Já existem muitas boas respostas neste tópico; provavelmente existem muitas razões diferentes para preferir o valor 0para ponteiros nulos, mas vou adicionar mais duas:

  • No C ++, a inicialização zero de um ponteiro o definirá como nulo.
  • Em muitos processadores, é mais eficiente definir um valor como 0 ou testá-lo igual / não igual a 0 do que para qualquer outra constante.

0

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.


-1

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.


-2

A constante 0é utilizada em vez de NULLporque C foi feita por alguns trilhões homens das cavernas de anos, NULL, NIL, ZIP, ou NADDAteria tudo feito muito mais sentido do que 0.

Mas como o endereçamento de memória começa em 0, 0 não é tão válido quanto qualquer outro?

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).

Como 0 pode ser usado para manipular ponteiros nulos, se for esse o caso?

Porque 0usado 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.

Por que um número negativo não é nulo?

Isso seria ainda mais confuso.


Seu ponto de vista sobre "homens das cavernas" etc. provavelmente está na raiz, embora eu ache que as especificidades são diferentes. As formas mais antigas do que evoluiu para C foram projetadas para rodar em uma arquitetura específica em que um intnão era apenas do mesmo tamanho que um ponteiro - em muitos contextos, um inte 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.
Supercat 23/09/2013

Desde então, o C evoluiu para que ele pudesse ser usado para escrever programas para uma enorme variedade de outras máquinas com diferentes representações de números e ponteiros. Enquanto constantes numéricas diferentes de zero raramente eram usadas como ponteiros, zeros numéricos constantes eram amplamente usados ​​para representar ponteiros nulos. Desabilitar esse uso teria quebrado o código existente, portanto, espera-se que os compiladores convertam um zero numérico em qualquer coisa que a implementação use para representar um ponteiro nulo.
Supercat 23/09

-4

( 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

  1. O fato de que zero é falso, então pode-se fazer diretamente em if(!my_ptr)vez de if(my_ptr==NULL).
  2. O fato de números inteiros globais não iniciados serem inicializados por padrão para todos os zeros e, como tal, um ponteiro de todos os zeros seria considerado não inicializado.

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:

C garante que zero nunca é um endereço válido para dados, portanto, um valor de retorno zero pode ser usado para sinalizar um evento anormal, nesse caso, sem espaço.

Ponteiro e números inteiros não são intercambiáveis, Zero é a única exceção: o zero constante pode ser atribuído a um ponteiro e um ponteiro pode ser comparado com o zero constante. A constante simbólica NULL é frequentemente usada no lugar de zero, como um mnemônico para indicar mais claramente que esse é um valor especial para um ponteiro. NULL é definido em. A partir de agora, usaremos NULL.

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 )

Os dois compiladores diferem nos detalhes em como eles lidam com isso. No anterior, o início é encontrado nomeando uma função; mais tarde, o início é simplesmente considerado 0. Isso indica que o primeiro compilador foi gravado antes de termos uma máquina com mapeamento de memória; portanto, a origem do programa não estava no local 0, enquanto que no momento do segundo, tivemos um PDP-11 que forneceu mapeamento.

(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.


1
Não diminuí a votação, mas sua postagem tem várias imprecisões de fato, por exemplo. é impossível acessar a memória física no deslocamento 0 (devido a todos os interruptores estarem desligados? realmente?), 0 e a constante 0 sendo intercambiável (eles podem não estar) e outros.
21713 Hasturkun

Em relação a 0 e ao zero constante, é isso que o livro original diz, e é isso que mostra o teste real: você encontrou uma diferença real entre os dois? Se sim, qual compilador e plataforma? Embora muitas respostas sugiram que exista uma diferença que eu não encontrei, elas não têm referência para mostrar a diferença. De fato, de acordo com en.wikipedia.org/wiki/Zero_page E também "Windows Via C / C ++ de Jeffrey Richter e Christophe Nasarre (publicado pela Microsoft Press)" na primeira página inteira! é protegida em computadores modernos, justamente para evitar nula (na verdade perdendo mais de um byte!)
halb yoel

Obviamente, o padrão de bits do endereço é usado para selecionar o que está sendo lido. Geralmente é o caso. de qualquer forma, eu não quero discutir com você, eu estava apenas apontando por que você pode ter sofrido uma votação negativa.
21713 Hasturkun

Eu não concordo com suas reivindicações. Também não estou interessado em continuar esta discussão.
Hasturkun

6
A reivindicação de hardware não faz sentido. Para ler o endereço zero, dirija! Chip Selecione baixo,! RAS alto,! CAS baixo,! WE alto e todas as linhas de endereço baixas. Quando o ônibus está desligado,! CS está alto.
MSalters
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.