Onde os valores nulos são armazenados ou eles são armazenados?

39

Quero aprender sobre valores nulos ou referências nulas.

Por exemplo, eu tenho uma classe chamada Apple e criei uma instância dela.

Apple myApple = new Apple("yummy"); // The data is stored in memory

Então eu comi aquela maçã e agora ela precisa ser nula, então a defino como nula.

myApple = null;

Após esta ligação, esqueci que comi e agora quero verificar.

bool isEaten = (myApple == null);

Com esta chamada, onde está o myApple fazendo referência? Null é um valor especial de ponteiro? Nesse caso, se eu tiver 1000 objetos nulos, eles ocupam 1000 espaço de memória de objeto ou 1000 int de espaço de memória se considerarmos um tipo de ponteiro como int?

Mert Akcakaya
fonte

Respostas:

45

No seu exemplo, myAppletem o valor especial null(normalmente todos os zero bits) e, portanto, não faz referência a nada. O objeto ao qual ele se referiu originalmente agora está perdido no heap. Não há como recuperar sua localização. Isso é conhecido como vazamento de memória em sistemas sem coleta de lixo.

Se você originalmente definir 1000 referências como nulas, terá espaço para apenas 1000 referências, geralmente 1000 * 4 bytes (em um sistema de 32 bits, duas vezes o número em 64). Se essas 1.000 referências apontarem originalmente para objetos reais, você alocou 1000 vezes o tamanho de cada objeto, mais espaço para as 1000 referências.

Em algumas linguagens (como C e C ++), os ponteiros sempre apontam para alguma coisa, mesmo quando "não inicializados". O problema é se o endereço que eles mantêm é legal para o seu programa acessar. O endereço especial zero (aka null) não é deliberadamente mapeado no seu espaço de endereço, portanto, uma falha de segmentação é gerada pela unidade de gerenciamento de memória (MMU) quando é acessada e seu programa trava. Porém, como o endereço zero não é deliberadamente mapeado, torna-se um valor ideal a ser usado para indicar que um ponteiro não está apontando para nada, daí seu papel como null. Para completar a história, conforme você aloca memória com newoumalloc(), o sistema operacional configura a MMU para mapear páginas da RAM para o seu espaço de endereço e elas se tornam utilizáveis. Ainda existem tipicamente amplos intervalos de espaço de endereço que não são mapeados e, portanto, também levam a falhas de segmentação.

Randall Cook
fonte
Muito boa explicação.
NoChance
6
Está um pouco errado na parte "vazamento de memória". É um vazamento de memória em sistemas sem gerenciamento automático de memória. No entanto, o GC não é a única maneira possível de implementar o gerenciamento automático de memória. C ++ std::shared_ptr<Apple>é um exemplo que nem GC nem vaza Applequando zerado.
MSalters
1
@MSalters - Não é shared_ptrapenas um formulário básico para coleta de lixo? O GC não exige que haja um "coletor de lixo" separado, apenas essa coleta de lixo ocorre.
Reponha Monica
5
@Brendan: O termo "coleta de lixo" é quase universalmente entendido como referência à coleta não determinística que ocorre independentemente do caminho normal do código. Destruição determinística baseada na contagem de referência é algo completamente diferente.
Mason Wheeler
1
Boa explicação. Um ponto um pouco enganador é a suposição de que a alocação de memória é mapeada para a RAM. A RAM é um mecanismo para armazenamento de memória de curto prazo, mas o mecanismo de armazenamento real é abstraído pelo sistema operacional. No Windows (para aplicativos sem anel zero), as páginas de memória são virtualizadas e podem ser mapeadas para RAM, arquivo de troca de disco ou talvez outro dispositivo de armazenamento.
Simon Gillbee
13

A resposta depende do idioma que você está usando.

C / C ++

Em C e C ++, a palavra-chave era NULL, e o que realmente era NULL era 0. Foi decidido que "0x0000" nunca seria um ponteiro válido para um objeto, e esse é o valor que é atribuído para indicar que ele é. não é um ponteiro válido. No entanto, é completamente arbitrário. Se você tentasse acessá-lo como um ponteiro, ele se comportaria exatamente como um ponteiro para um objeto que não existe mais na memória, causando uma exceção de ponteiro inválida. O ponteiro em si ocupa memória, mas não mais do que um objeto inteiro. Portanto, se você tiver 1000 ponteiros nulos, será equivalente a 1000 números inteiros. Se alguns desses ponteiros apontarem para objetos válidos, o uso da memória seria equivalente a 1000 números inteiros mais a memória contida nesses ponteiros válidos. Lembre-se de que em C ou C ++,não implica que a memória foi liberada, portanto, você deve excluir explicitamente esse objeto usando dealloc (C) ou delete (C ++).

Java

Diferentemente de C e C ++, em Java nulo é apenas uma palavra-chave. Em vez de gerenciar nulo como um ponteiro para um objeto, ele é gerenciado internamente e tratado como um literal. Isso eliminou a necessidade de vincular ponteiros como tipos inteiros e permite que o Java abstraia completamente os ponteiros. No entanto, mesmo que o Java oculte melhor, eles ainda são ponteiros, o que significa que 1000 ponteiros nulos ainda consomem o equivalente a 1000 números inteiros. Obviamente, quando apontam para objetos, como C e C ++, a memória é consumida por esses objetos até que não haja mais indicadores sobre eles; no entanto, ao contrário de C e C ++, o coletor de lixo o pega na próxima passagem e libera a memória, sem exigir que você tenha que rastrear quais objetos estão liberados e quais não, na maioria dos casos (a menos que você tenha motivos para fazer referência fraca a objetos, por exemplo).

Neil
fonte
9
Sua distinção não está correta: de fato, em C e C ++, o ponteiro nulo não precisa apontar para o endereço de memória 0 (embora essa seja a implementação natural, igual a Java e C #). Pode apontar literalmente para qualquer lugar. Isso é um pouco confuso pelo fato de que literal-0 pode ser implicitamente convertido em um ponteiro nulo. Mas o padrão de bits armazenado para um ponteiro nulo ainda não precisa ser todo de zeros.
Konrad Rudolph
1
Não, você está errado. A semântica é completamente transparente ... no programa, ponteiros nulos e a macro NULL( não uma palavra-chave, a propósito) são tratados como se fossem zero bits. Mas eles não precisam ser implementadas, como tal, e de fato algumas implementações obscuros fazer uso diferentes de zero ponteiros nulos. Se eu escrever if (myptr == 0), o compilador fará a coisa correta, mesmo que o ponteiro nulo seja representado internamente por 0xabcdef.
Konrad Rudolph
3
@ Neil: uma constante de ponteiro nulo (prvalor do tipo inteiro que avalia como zero) é convertível em um valor de ponteiro nulo . (§4.10 C ++ 11.) Não é garantido que um valor de ponteiro nulo tenha todos os bits zero. 0é uma constante de ponteiro nulo, mas isso não significa que myptr == 0verifica se todos os bits de myptrsão zero.
Mat
5
@Neil: Você pode querer verificar esta entrada no FAQ C ou esta questão SO
hugomg
1
@ Neil É por isso que me esforcei para não mencionar a NULLmacro, em vez de falar sobre o "ponteiro nulo" e mencionar explicitamente que "o literal-0 pode ser implicitamente convertido em um ponteiro nulo".
Konrad Rudolph
5

Um ponteiro é simplesmente uma variável que é principalmente de um tipo inteiro. Ele especifica um endereço de memória em que o objeto real está armazenado.

A maioria dos idiomas permite acessar membros do objeto através desta variável de ponteiro:

int localInt = myApple.appleInt;

O compilador sabe como acessar os membros de um Apple. "Segue" o ponteiro para myAppleo endereço e recupera o valor doappleInt

Se você atribuir o ponteiro nulo a uma variável de ponteiro, faça o ponteiro apontar para nenhum endereço de memória. (O que torna o acesso do membro impossível.)

Para cada ponteiro, você precisa de memória para manter o valor inteiro do endereço de memória (principalmente 4 bytes em sistemas de 32 bits, 8 bytes em sistemas de 64 bits). Isso também é verdadeiro para ponteiros nulos.

Stephan
fonte
Eu acho que as variáveis ​​/ objetos de referência não são exatamente indicadores. Se você imprimi-los, eles contêm ClassName @ Hashcode. A JVM usa internamente o Hashtable para armazenar o Hashcode com o endereço real e usa um algoritmo Hash para recuperar o endereço real quando necessário.
minuseven)
@minusSeven Isso é correto para objetos literais, como números inteiros. Caso contrário, a hashtable mantém ponteiros para outros objetos contidos na própria classe Apple.
819 Neil
@minusSeven: Eu concordo. Os detalhes da implementação do ponteiro dependem muito do idioma / tempo de execução. Mas acho que esses detalhes não são relevantes para a questão específica.
840 Stephan
4

Exemplo rápido (note que os nomes variáveis ​​não são armazenados):

void main()
{
  int X = 3;
  int *Y = X;
  int *Z = null;
} // void main(...)


...........................
....+-----+--------+.......
....|     |   X    |.......
....+-----+--------+.......
....| 100 |   3    |<---+..
....+-----+--------+....|..
........................|..
....+-----+--------+....|..
....|     |   Y    |....|..
....+-----+--------+....|..
....| 102 |  100   +----+..
....+-----+--------+.......
...........................
....+-----+--------+.......
....|     |   z    |.......
....+-----+--------+.......
....| 104 |   0    |.......
....+-----+--------+.......
...........................

Felicidades.

umlcat
fonte