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

Em C (ou C ++), os pointers são especiais se tiverem o valor zero: Preciso definir pointers para zero depois de liberar sua memory, porque isso significa que liberar o ponteiro novamente não é perigoso; quando eu chamo malloc, ele retorna um ponteiro com o valor zero se não puder me pegar na memory; Eu uso if (p != 0) o tempo todo para garantir que os pointers passados ​​sejam válidos, etc.

Mas como o endereçamento de memory começa em 0, o 0 não é apenas um endereço válido como qualquer outro? Como 0 pode ser usado para manipular pointers nulos se for esse o caso? Por que não é um número negativo nulo?


Editar:

Um monte de boas respostas. Vou resumir o que foi dito nas respostas expressas como minha própria mente interpreta e espero que a comunidade me corrija se eu não entender.

  • Como tudo mais na programação, é uma abstração. Apenas uma constante, não 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 traduzi-lo para algum outro número, desde que ele nunca seja igual a um endereço “real” e seja igual a outro ponteiro nulo 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 está fora dos limites do programador.

  • Minha sugestão de número negativo foi um pouco de brainstorming selvagem, eu admito. Usar um inteiro sinalizado para endereços é um pouco desnecessário se isso significar que, além do ponteiro nulo (-1 ou qualquer outro), o espaço de valor é dividido igualmente entre inteiros positivos que fazem endereços válidos e números negativos que são apenas desperdiçados.

  • Se qualquer número é sempre representável por um tipo de dado, é 0. (Provavelmente 1 também é. Penso no inteiro de um bit que seria 0 ou 1 se não assinado, ou apenas no bit assinado se assinado, ou no inteiro de dois bits que seria [-2, 1]. Mas então você poderia simplesmente ir para 0 sendo nulo e 1 sendo o único byte acessível na memory.)

Ainda há algo que não está resolvido em minha mente. A pergunta de estouro de pilha Ponteiro para um endereço fixo específico me diz que mesmo se 0 para ponteiro nulo é uma abstração, outros valores de ponteiro não são necessariamente. Isso me leva a postar outra pergunta sobre estouro de pilha. Eu poderia querer acessar o endereço zero? .

2 pontos:

  • somente o valor constante 0 no código-fonte é o ponteiro nulo – a implementação do compilador pode usar qualquer valor que queira ou precise no código de execução. Algumas plataformas têm um valor de ponteiro especial que é ‘inválido’ que a implementação pode usar como o ponteiro nulo. A FAQ do C tem uma pergunta: “Sério, alguma máquina real realmente usou pointers nulos diferentes de zero, ou representações diferentes para pointers para tipos diferentes?” , que indica várias plataformas que usaram essa propriedade de 0 como o ponteiro nulo na origem C, enquanto representadas de forma diferente no tempo de execução. O padrão C ++ tem uma nota que deixa claro que converter “uma expressão constante integral com valor zero sempre produz um ponteiro nulo, mas a conversão de outras expressões que possuem valor zero não precisa produzir um ponteiro nulo”.

  • um valor negativo pode ser tão utilizável pela plataforma quanto um endereço – o padrão C simplesmente tinha que escolher algo para indicar um ponteiro nulo, e zero era escolhido. Eu sinceramente não tenho certeza se outros valores sentinela foram considerados.

Os únicos requisitos para um ponteiro nulo são:

  • é garantido para comparar desigual para um ponteiro para um object real
  • quaisquer dois pointers nulos irão comparar o mesmo (o C ++ refina isso de tal forma que isso só precisa ser mantido por pointers para o mesmo tipo)

Historicamente, o espaço de endereço começando em 0 era sempre ROM, usado para algumas rotinas de sistema operacional ou interrupção de manipulação 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, especificamente NÃO aloca nada no endereço 0.

IIRC, o valor “ponteiro nulo” não é garantido como zero. O compilador traduz 0 em qualquer valor “nulo” é apropriado para o sistema (que na prática é provavelmente zero, mas não necessariamente). A mesma tradução é aplicada sempre que você compara um ponteiro contra zero. Como você só pode comparar pointers um contra o outro e contra esse valor especial 0, ele isola o programador de saber qualquer coisa sobre a representação de memory do sistema. Quanto ao porquê eles escolheram 0 em vez de 42 ou algo assim, eu acho que é porque a maioria dos programadores começa a contar em 0 🙂 (Também, na maioria dos sistemas 0 é o primeiro endereço de memory e eles queriam que fosse conveniente, já que em as traduções práticas, como as que estou descrevendo, raramente acontecem; a linguagem apenas as permite).

Você deve estar entendendo mal o significado de zero constante no contexto do ponteiro.

Nem em C nem em pointers C ++ podem “ter valor zero”. Ponteiros não são objects aritméticos. Eles não podem ter valores numéricos como “zero” ou “negativo” ou qualquer coisa dessa natureza. Portanto, sua declaração sobre “pointers … tem o valor zero” simplesmente não faz sentido.

Em C & C ++, os pointers 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 configurações o valor do ponteiro nulo é representado fisicamente por um valor real de endereço zero. No entanto, se em algum endereço de plataforma 0 for realmente usado para algum propósito (ou seja, você pode precisar criar objects no endereço 0), o valor de ponteiro nulo em tal plataforma provavelmente será diferente. Pode ser fisicamente representado como valor de endereço 0xFFFFFFFF ou como valor de endereço 0xBAADBAAD , 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 os pointers nulos pela 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ê quer e traduzi-lo na representação do valor do ponteiro nulo, ou seja, traduzi-lo no código que colocará o valor do endereço de 0xFFFFFFFF no ponteiro p , por exemplo.

Em suma, o fato de você usar 0 no seu código de fonte para gerar valores de ponteiro nulo não significa que o valor do ponteiro nulo esteja de alguma forma associado ao endereço 0 . O 0 que você usa em 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”.

Mas como o endereçamento de memory começa em 0, o 0 não é apenas um endereço válido como qualquer outro?

Em alguns / muitos / todos os sistemas operacionais, o endereço de memory 0 é especial de alguma forma. Por exemplo, é frequentemente mapeado para memory inválida / inexistente, o que causa uma exceção se você tentar acessá-lo.

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

Eu acho que os valores de ponteiro são normalmente tratados como números não assinados: caso contrário, por exemplo, um ponteiro de 32 bits só seria capaz de endereçar 2 GB de memory, em vez de 4 GB.

Meu palpite seria que o valor mágico 0 foi escolhido para definir um ponteiro inválido, já que ele poderia ser testado com menos instruções. Algumas linguagens de máquina automaticamente configurariam os bits de zero e de sinal ao carregar registradores para que você pudesse testar um ponteiro nulo com uma carga simples e, em seguida, ramificar as instruções sem fazer uma carga, comparar e depois ramificar.

No Commodore Pet, Vic20 e C64, que foram as primeiras máquinas em que trabalhei, a RAM começou no local 0, então era totalmente válido ler e escrever usando um ponteiro nulo se você realmente quisesse.

Eu acho que é apenas uma convenção. Deve haver algum valor para marcar um ponteiro inválido.

Você acabou de perder um byte de espaço de endereço, que raramente deveria ser um problema.

Não há pointers negativos. Os pointers são sempre sem sinal. Além disso, se eles pudessem ser negativos, sua convenção significaria perder metade do espaço de endereço.

Embora C use 0 para representar o ponteiro nulo, tenha em mente que o valor do próprio ponteiro pode não ser zero. No entanto, a maioria dos programadores sempre usará sistemas em que o ponteiro nulo é, na verdade, 0.

Mas por que zero? Bem, é um endereço que todo sistema compartilha. E, muitas vezes, os endereços baixos são reservados para propósitos do sistema operacional, portanto, o valor funciona bem como estando fora dos limites dos programas aplicativos. A atribuição acidental de um valor inteiro a um ponteiro é tão provável que acabe zero como qualquer outra coisa.

Historicamente, a baixa memory de um aplicativo era ocupada por resources do sistema. Foi naqueles dias que o zero se tornou o valor nulo padrão.

Embora isso não seja necessariamente verdadeiro para sistemas modernos, ainda é uma má idéia definir valores de ponteiro para qualquer coisa, exceto o que a alocação de memory lhe forneceu.

Em relação ao argumento sobre não definir um ponteiro para nulo após excluí-lo para que futuras exclusões “exponha erros” …

Se você está realmente preocupado com isso, então uma abordagem melhor, que é garantida para funcionar, é alavancar assert ():

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

Isso requer alguma digitação extra e uma verificação extra durante as compilações de debugging, mas é certo que você terá o que deseja: observe quando ptr é excluído “duas vezes”. A alternativa dada na discussão de comentários, não definindo o ponteiro como nulo, assim você terá uma falha, simplesmente não é garantido que será bem-sucedido. Pior, ao contrário do que foi dito acima, pode causar uma falha (ou muito pior!) Em um usuário se um desses “bugs” passar pela prateleira. Finalmente, esta versão permite que você continue a executar o programa para ver o que realmente acontece.

Eu percebo que isso não responde à pergunta, mas eu estava preocupado que alguém lendo os comentários chegue à conclusão de que é considerado ‘boa prática’ NÃO definir pointers para 0 se for possível que eles sejam enviados para livre () ou elimine duas vezes. Nesses poucos casos, quando é possível, NUNCA é uma boa prática usar o comportamento indefinido como uma ferramenta de debugging. 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 demoram horas para caçar e quase sempre afetam o programa de uma forma totalmente inesperada, o que é difícil de ser rastreado até o problema original.

Em uma das antigas máquinas DEC (PDP-8, eu acho), o tempo de execução C protegeria a memory da primeira página, para que qualquer tentativa de acessar a memory naquele bloco causasse uma exceção.

A escolha do valor de sentinela é arbitrária, e isso está sendo tratado pela próxima versão do C ++ (informalmente conhecida como “C ++ 0x”, mais provavelmente 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 de 0 pode ser usado como uma expressão de boot para qualquer POD e para qualquer object com um construtor padrão e tem o significado especial de atribuir o valor de sentinela no caso de uma boot de ponteiro. Quanto ao motivo pelo qual um valor negativo não foi escolhido, os endereços normalmente variam de 0 a 2 N -1 para algum valor N. Em outras palavras, os endereços são geralmente 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 memory, enquanto 0 seria sempre um endereço representável. Também é usado por razões históricas, como o endereço de memory 0 era tipicamente inutilizável em programas, e atualmente a maioria dos sistemas operacionais tem partes do kernel carregadas na (s) página (s) inferior (es) de memory, e tais páginas são tipicamente protegidas de tal forma que se tocado (desreferenciado) por um programa (salvar o kernel) causará uma falha.

Tem que ter algum valor. Obviamente, você não quer impor valores que o usuário possa legitimamente querer usar. Eu especularia que, como o tempo de execução C fornece o segmento BSS para dados inicializados com zero, faz um certo grau de sentido interpretar zero como um valor de ponteiro não inicializado.

Uma razão importante pela qual muitos sistemas operacionais usam todos os bits-zero para a representação de ponteiro nulo, é que isso significa memset(struct_with_pointers, 0, sizeof struct_with_pointers) e similar configurará todos os pointers dentro de struct_with_pointers para pointers nulos. Isso não é garantido pelo padrão C, mas muitos, muitos programas assumem isso.

Raramente um sistema operacional permite escrever no endereço 0. É comum colocar itens específicos do sistema operacional em pouca memory; 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 está a parte superior da RAM.) E nenhum sistema operacional em seu juízo perfeito permitirá que você editar tabelas do sistema a sério ou não.

Isso pode não estar nas mentes da K & R quando eles criaram o C, mas ele (junto com o fato de que 0 == null é bem fácil de lembrar) faz do 0 uma escolha popular.

O valor 0 é um valor especial que assume vários significados em expressões específicas. No caso dos pointers, como já foi apontado muitas vezes, é usado provavelmente porque na época era a maneira mais conveniente de dizer “insira o valor sentinela padrão aqui”. Como uma expressão constante, não tem o mesmo significado que zero bit a bit (ou seja, todos os bits definidos como zero) no contexto de uma expressão de ponteiro. Em C ++, existem vários tipos que não têm uma representação zero bit a bit de NULL , como membro de ponteiro e ponteiro para function de membro.

Felizmente, o C ++ 0x tem uma nova palavra-chave para “expressão que significa um ponteiro inválido conhecido que também não mapeia para bit a zero para expressões integrais”: nullptr . Embora existam alguns sistemas que você pode direcionar com C ++ que permitem a desreferenciação do endereço 0 sem barfing, então programador cuidado.

Já existem muitas respostas boas neste tópico; provavelmente existem muitas razões diferentes para preferir o valor 0 para pointers nulos, mas adicionarei mais dois:

  • Em C ++, zero-inicializando um ponteiro irá defini-lo como nulo.
  • Em muitos processadores, é mais eficiente definir um valor como 0 ou testá-lo como igual / diferente de 0 do que para qualquer outra constante.

Isso depende da implementação de pointers em C / C ++. Não há nenhuma razão específica pela qual o NULL seja equivalente em atribuições a um ponteiro.

Existem razões históricas para isso, mas também há razões de otimização para isso.

É comum que o sistema operacional forneça um processo com páginas de memory inicializadas em 0. Se um programa quiser interpretar parte dessa página de memory como um ponteiro, ele será 0, portanto, é bastante fácil para o programa determinar que esse ponteiro é não inicializado. (isso não funciona tão bem quando aplicado a páginas flash não inicializadas)

Outra razão é que, em muitos processadores, é muito fácil testar a equivalência de um valor para 0. Às vezes, é feita uma comparação gratuita 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 stream de instruções para comparar.

As comparações baratas para a maioria dos processadores são assinadas com menos de 0 e iguais a 0. (assinadas acima de 0 e não iguais a 0 estão implícitas por ambas)

Uma vez que 1 valor de todos os valores possíveis precisa ser reservado como ruim ou não inicializado, você também pode torná-lo aquele que tem o teste mais barato para equivalência ao valor ruim. Isso também é verdadeiro para cadeias de caracteres terminadas ‘\ 0’.

Se você tentasse usar maior ou menor que 0 para essa finalidade, acabaria cortando o seu intervalo de endereços pela metade.

A constante 0 é usada em vez de NULL porque C foi feita por alguns homens das cavernas trilhões de anos atrás, NULL , NIL , ZIP ou NADDA teriam feito muito mais sentido do que 0 .

Mas como o endereçamento de memory começa em 0, o 0 não é apenas um endereço válido como qualquer outro?

De fato. Embora muitos sistemas operacionais o impeçam 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 erros de referência de ponteiro nulo são muito comuns, decidiram “consertá-los” código do userspace para mapear para a página 0; portanto, se você chamar um retorno de chamada, mas o ponteiro do retorno de chamada for NULL, você não acabará executando algum código arbitrário).

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

Porque 0 usado em comparação com um ponteiro será substituído por algum valor específico de implementação , que é o valor de retorno de malloc em uma falha de malloc.

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

Isso seria ainda mais confuso.

( Por favor, leia este parágrafo antes de ler o post. Estou pedindo a alguém interessado em ler este post que tente ler atentamente e, claro, não o faça até que você o entenda completamente, obrigado.)

Agora é wiki da comunidade, como tal, se alguém discordar de algum dos conceitos, modifique-o, com uma explicação clara e detalhada do que está errado e por quê e, se possível, cite fonts ou forneça provas que possam ser reproduzidas.

Responda

Aqui estão alguns outros motivos que podem ser os fatores subjacentes para NULL == 0

  1. O fato de que zero é falso, então pode-se fazer diretamente if(!my_ptr) invés de if(my_ptr==NULL) .
  2. O fato de que inteiros globais não iniciados são 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 em 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, então porque não usar o índice 0 de uma matriz para manter seu tamanho?

Na verdade, 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? They would rather provide a keyword null (as many other languages do) rather than mapping zero to NULL!

As such while as of today it might just syntactic sugar, it is clear that the original intention of the C language developers was not for syntactic sugar, as I will show further.

1) The Specification

Yet while it is true that the C specification speak from the constant 0 as the null pointer (section 6.3.2.3), and also define NULL to be implementation defined (section 7.19 in the C11 specification, and 7.17 in the C99 specification), the fact remains that in the book “The C Programming Language” written by the inventors of C the following is stated in section 5.4:

C guarantees that zero is never a valid address for data, so a return value of zero can be used to signal an abnormal event, in this case, no space.

Pointer and integers are not interchangeable, Zero is the sole exception: the constant zero may be assigned to a pointer, and a pointer may be compared with the constant zero. The symbolic constant NULL is often used in place of zero, as a mnemonic to indicate more clearly that this is a special value for a pointer. NULL is defined in . We will use NULL henceforth.

As one can see (from the words “zero address”) at least the original intention of the authors of C were of the address zero, and not the constant zero, moreover it appears from this excerpt that the reason why the specification speaks from the constant zero is probably not to exclude an expression that evaluates to zero, but instead to include the integer constant zero to be the only integer constant allowed for use in a pointer context without casting.

2) Summary

While the specification does not say explicitly that a zero address can be treated different than the zero constant, it does not say that not, and the fact the when dealing with the null-pointer constant it does not claim it to be implementation defined as it does by the NULL defined constant, instead claim it to be zero, shows that there might be a difference between the zero constant and the zero address.

(However if this is the case I just wonder why NULL is implementation defined, since in such a case NULL can also be the constant zero, as the compiler anyway has to convert all zero constants into the actual implementation defined NULL?)

However I don not see this in real action, and in the general platforms the address zero and the constant zero are treated the same, and throw the same error message.

Furthermore the fact is that today’s operating systems are actually reserving the entire first page (range 0x0000 to 0xFFFF), just to prevent access to the zero address because of C’s NULL pointer, (see http://en.wikipedia.org/wiki/Zero_page , as well as “Windows Via C/C++ by Jeffrey Richter and Christophe Nasarre (published by Microsoft Press)”).

Thus I would ask from anyone claiming to actually have it seen in action, to please specify the platform, and compiler, and the exact code he actually did, (although due to the vague definition in the specification [as I have shown] any compiler and platform is free to do whatever he wants).

However it apparently seems that the authors of C didn’t had this in mind, and they were speaking of the “zero address”, and that “C guarantees that it is never a valid address”, as well as “NULL is just a mnemonic”, clearly showing that it’s original intention was not for “syntactic sugar”.

Not Because Of The Operating System

Also claiming that the operating system denies access to address zero, for a few reasons:

1) When C was written there was no such restriction, as one can see on this wikipage http://en.wikipedia.org/wiki/Zero_page .

2) The fact is that C compilers did accessed memory address zero.

This appears to be the fact from the following paper by BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )

The two compilers differ in the details in how they cope with this. In the earlier one, the start is found by naming a function; in the later, the start is simply taken to be 0. This indicates that the first compiler was written before we had a machine with memory mapping, so the origin of the program was not at location 0, whereas by the time of the second, we had a PDP-11 that did provide mapping.

(In fact as of today (as I cited references above from wikipedia and microsoft press), the reason for restricting access to the zero address is because of C’s NULL pointers! So at the end it turns out to be the other way around!)

3) Remember that C is also used to write operating systems, and even C compilers!

In fact C was developed for the purpose of writing the UNIX operating system with it, and as such it appears to be no reason why they should restrict themselves from address zero.

(Hardware) Explanation On How Computers Are (Physically) Able To Access Address Zero

There is another point I want to explain here, how is it possible to reference address zero at all?

Think of it for a second, the addresses are fetched by the processor, and then sent as voltages on the memory bus, which is then used by the memory system to get to the actual address, and yet a address of zero will mean no voltage, so how is the physical hardware of the memory system accessing address zero?

The answer appears to be, that address zero is the default, and in other words address zero is always accessible by the memory system when the memory bus is completly off, and as such any request to read or write without specifying an actual address (which is the case with address zero) is automatically accessing address zero.