Por que os ponteiros não são inicializados com NULL por padrão?

118

Alguém pode explicar por que os ponteiros não foram inicializados com NULL?
Exemplo:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

O programa não entraria no if porque bufnão é nulo.

Gostaria de saber por que, em que caso precisamos de uma variável com lixo ativado, especialmente ponteiros para endereçar o lixo na memória?

Jonathan
fonte
13
Bem, porque os tipos fundamentais não são inicializados. Portanto, suponho que sua pergunta "real": por que os tipos fundamentais não são inicializados?
GManNickG
11
"o programa não entraria no if porque buf não é nulo". Isso não é correto. Como você não sabe o que é buf , não pode saber o que não é .
Drew Dormann
Em contraste com algo como Java, C ++ dá muito mais responsabilidade ao desenvolvedor.
Rishi de
inteiros, ponteiros, o padrão é 0 se você usar o construtor ().
Erik Aronesty de
Devido ao pressuposto de que alguém que usa C ++ sabe o que está fazendo, além disso, alguém que usa ponteiros brutos sobre um ponteiro inteligente sabe (ainda mais) o que está fazendo!
Lofty Lion

Respostas:

161

Todos nós percebemos que o ponteiro (e outros tipos de POD) deve ser inicializado.
A questão então se torna 'quem deve inicializá-los'.

Bem, existem basicamente dois métodos:

  • O compilador os inicializa.
  • O desenvolvedor os inicializa.

Vamos supor que o compilador inicializou qualquer variável não inicializada explicitamente pelo desenvolvedor. Em seguida, nos deparamos com situações em que a inicialização da variável não era trivial e o motivo pelo qual o desenvolvedor não o fez no ponto de declaração foi que ele precisava executar alguma operação e depois atribuir.

Portanto, agora temos a situação em que o compilador adicionou uma instrução extra ao código que inicializa a variável como NULL e, posteriormente, o código do desenvolvedor é adicionado para fazer a inicialização correta. Ou, em outras condições, a variável potencialmente nunca é usada. Muitos desenvolvedores de C ++ gritariam mal em ambas as condições ao custo dessa instrução extra.

Não é apenas uma questão de tempo. Mas também espaço. Existem muitos ambientes em que ambos os recursos são escassos e os desenvolvedores também não querem desistir.

MAS : Você pode simular o efeito de forçar a inicialização. A maioria dos compiladores avisa sobre variáveis ​​não inicializadas. Portanto, sempre coloco meu nível de alerta no nível mais alto possível. Em seguida, diga ao compilador para tratar todos os avisos como erros. Sob essas condições, a maioria dos compiladores irá gerar um erro para variáveis ​​que não foram inicializadas, mas são usadas e, assim, evitarão que o código seja gerado.

Martin York
fonte
5
Bob Tabor disse: “Muitas pessoas não deram atenção suficiente à inicialização!” É 'amigável' inicializar todas as variáveis ​​automaticamente, mas leva tempo, e programas lentos são 'hostis'. Uma planilha ou editores que mostrassem o malloc de lixo aleatório encontrado seriam inaceitáveis. C, uma ferramenta afiada para usuários treinados (perigosa se mal utilizada), não deve demorar para inicializar variáveis ​​automáticas. Uma macro de roda de treinamento para variáveis ​​de inicialização poderia ser, mas muitos acham que é melhor se levantar, estar atento e sangrar um pouco. Em apuros, você trabalha da maneira que pratica. Portanto, pratique como você gostaria que fosse.
Bill IV,
2
Você ficaria surpreso com a quantidade de bugs que seriam evitados apenas por alguém consertando toda a inicialização. Isso seria um trabalho tedioso se não fosse pelos avisos do compilador.
Jonathan Henson
4
@Loki, estou tendo dificuldade em entender seu ponto. Eu estava apenas tentando elogiar sua resposta como útil, espero que você tenha percebido. Se não, sinto muito.
Jonathan Henson
3
Se o ponteiro for primeiro definido como NULL e, em seguida, definido como qualquer valor, o compilador deve ser capaz de detectar isso e otimizar a primeira inicialização NULL, certo?
Korchkidu
1
@Korchkidu: Às vezes. Um dos maiores problemas, porém, é que não há como avisar que você se esqueceu de fazer a inicialização, pois não pode saber que o padrão não é perfeito para seu uso.
Deduplicator
41

Citando Bjarne Stroustrup em TC ++ PL (Edição Especial p.22):

A implementação de um recurso não deve impor sobrecargas significativas aos programas que não o exigem.

John
fonte
e também não dá a opção. Parece
Jonathan
8
@ Jonathan nada impede que você inicialize o ponteiro para nulo - ou para 0 como é padrão em C ++.
stefanB
8
Sim, mas Stroustrup poderia ter feito a sintaxe padrão favorecer a correção do programa em vez do desempenho, inicializando o ponteiro a zero e fazendo com que o programador tivesse que solicitar explicitamente que o ponteiro não fosse inicializado. Afinal, a maioria das pessoas prefere corrigir-mas-devagar a rápido-mas-errado, porque geralmente é mais fácil otimizar uma pequena quantidade de código do que corrigir bugs em todo o programa. Especialmente quando muito disso pode ser feito por um compilador decente.
Robert Tuck
1
Não quebra a compatibilidade. A ideia foi considerada em conjunto com "int * x = __uninitialized" - segurança por padrão, velocidade por intenção.
MSalters
4
Eu gosto do que Dfaz Se você não quiser a inicialização, use esta sintaxe float f = void;ou int* ptr = void;. Agora ele é inicializado por padrão, mas se você realmente precisar, pode parar o compilador de fazê-lo.
deft_code
23

Porque a inicialização leva tempo. E em C ++, a primeira coisa que você deve fazer com qualquer variável é inicializá-la explicitamente:

int * p = & some_int;

ou:

int * p = 0;

ou:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

fonte
1
k, se a inicialização levar tempo e eu ainda quiser, há alguma forma de tornar meus ponteiros nulos sem configurá-lo manualmente? veja, não porque eu não queira corrigir isso, porque parece que nunca vou usar ponteiros unitiliazed com lixo em seu endereço
Jonathan
1
Você inicializa os membros da classe no construtor da classe - é assim que o C ++ funciona.
3
@Jonathan: mas null também é lixo. Você não pode fazer nada útil com um ponteiro nulo. Desreferenciá-lo é um erro. Crie ponteiros com valores adequados, não nulos.
DrPizza
2
Inicializar o apointer para Nnull pode ser uma coisa sensata a fazer. E existem várias operações que você pode executar em ponteiros nulos - você pode testá-los e pode chamar delete neles.
4
Se você nunca vai usar um ponteiro sem inicializá-lo explicitamente, não importa o que ele continha antes de atribuir um valor a ele e, de acordo com o princípio C e C ++ de pagar apenas pelo que você usa, isso não é feito automaticamente. Se houver um valor padrão aceitável (geralmente o ponteiro nulo), você deve inicializá-lo. Você pode inicializar ou deixar não inicializado, sua escolha.
David Thornley
20

Porque um dos lemas do C ++ é:


Você não paga pelo que não usa


Por isso mesmo, o operator[]da vectorclasse não verifica se o índice está fora dos limites, por exemplo.

KeatsPeeks
fonte
12

Por razões históricas, principalmente porque é assim que é feito em C. Por que é feito assim em C, é outra questão, mas acho que o princípio do overhead zero estava envolvido de alguma forma nesta decisão de design.

AraK
fonte
Acho que porque C é considerada uma linguagem de nível inferior com fácil acesso à memória (também conhecido como ponteiros), portanto, dá a você liberdade para fazer o que quiser e não impõe sobrecarga inicializando tudo. BTW, eu acho que depende da plataforma porque eu trabalhei em uma plataforma móvel baseada em Linux que inicializou toda a sua memória para 0 antes de usar, então todas as variáveis ​​seriam definidas como 0.
stefanB
8

Além disso, temos um aviso para quando você explodir: "é possivelmente usado antes de um valor atribuído" ou verbage semelhante dependendo do seu compilador.

Você compila com avisos, certo?

Joshua
fonte
E isso é apenas possível como um reconhecimento de que o rastreamento do compilador pode estar com defeito.
Deduplicator
6

Existem poucas situações em que faz sentido que uma variável não seja inicializada, e a inicialização padrão tem um custo pequeno, então por que fazer isso?

C ++ não é C89. Inferno, mesmo C não é C89. Você pode misturar declarações e código, então você deve adiar a declaração até o momento em que você tenha um valor adequado para inicializar.

DrPizza
fonte
2
Então, apenas cada valor precisará ser escrito duas vezes - uma vez pela rotina de configuração do compilador e novamente pelo programa do usuário. Normalmente não é um grande problema, mas aumenta (por exemplo, se você estiver criando uma matriz de 1 milhão de itens). Se você quiser a inicialização automática, você sempre pode criar seus próprios tipos que fazem isso; mas, dessa forma, você não é forçado a aceitar despesas desnecessárias se não quiser.
Jeremy Friesner
3

Um ponteiro é apenas outro tipo. Se você criar um int, charou qualquer outro POD tipo não é inicializado para zero, então por que um ponteiro? Isso pode ser considerado uma sobrecarga desnecessária para alguém que escreve um programa como este.

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

Se você sabe que irá inicializá-lo, por que o programa deveria incorrer em um custo quando você cria pela primeira vez pBufna parte superior do método? Este é o princípio de sobrecarga zero.

LeopardSkinPillBoxHat
fonte
1
por outro lado, você poderia fazer char * pBuf = condition? novo char [50]: m_myMember-> buf (); É mais uma questão de sintaxe do que de eficiência, mas ainda assim concordo com você.
the_drow
1
@the_drow: Bem, pode-se tornar mais complexo apenas para que tal reescrita não seja possível.
Deduplicator
2

Se quiser um ponteiro que sempre é inicializado como NULL, você pode usar um modelo C ++ para emular essa funcionalidade:

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};
Adisak
fonte
1
Se eu estivesse implementando isso, não me incomodaria com o copy ctor ou a atribuição de op - os padrões são bastante OK. E o seu destruidor é inútil. É claro que você também pode testar ponteiros usando o operador inferior (et all) em algumas circunstâncias), portanto, deve fornecê-los.
OK, menos do que é trivial para implementar. Eu tinha o destruidor para que, se o objeto sair do escopo (ou seja, local definido dentro de um subescopo de uma função), mas ainda estiver ocupando espaço na pilha, a memória não será deixada como um ponteiro pendente para o lixo. Mas cara sério, eu escrevi isso em menos de 5 min. Não foi feito para ser perfeito.
Adisak
OK adicionou todos os operadores de comparação. As substituições padrão podem ser redundantes, mas estão aqui explicitamente, pois este é um exemplo.
Adisak
1
Eu não conseguia entender como isso tornaria todos os ponteiros nulos sem configurá-los manualmente, você poderia explicar o que vc fez aqui, por favor?
Jonathan
1
@ Jonathan: Este é basicamente um "ponteiro inteligente" que não faz nada além de definir o ponteiro como nulo. IE em vez de Foo *a, você usa InitializedPointer<Foo> a- Um exercício puramente acadêmico, pois Foo *a=0é menos digitação. No entanto, o código acima é muito útil do ponto de vista educacional. Com uma pequena modificação (para o ctor / dtor e operações de atribuição de "espaço reservado"), ele poderia ser facilmente estendido para vários tipos de ponteiros inteligentes, incluindo ponteiros com escopo (que são liberados no destruidor) e ponteiros de contagem de referência adicionando inc / operações dec quando o m_pPointer é definido ou limpo.
Adisak
2

Observe que os dados estáticos são inicializados com 0 (a menos que você diga o contrário).

E sim, você deve sempre declarar suas variáveis ​​o mais tarde possível e com um valor inicial. Código como

int j;
char *foo;

deve disparar sinos de alarme quando você lê-lo. Não sei se algum fiapo pode ser persuadido a reclamar disso, já que é 100% legal.

pm100
fonte
isso é GARANTIDO ou apenas uma prática comum usada pelos compiladores de hoje?
gha.st
1
variáveis ​​estáticas são inicializadas com 0, o que faz a coisa certa para ponteiros também (ou seja, define-os como NULL, nem todos os bits 0). Este comportamento é garantido pela norma.
Alok Singhal
1
a inicialização de dados estáticos para zero é garantida pelo padrão C e C ++, não é apenas uma prática comum
groovingandi
1
talvez porque algumas pessoas querem ter certeza de que sua pilha está bem alinhada, eles pré-declaram todas as variáveis ​​no topo da função? Talvez eles estejam escrevendo em um dialeto AC que EXIGE isso?
KitsuneYMG
1

Outra possível razão, é que em ponteiros de tempo de link é dado um endereço, mas o endereçamento indireto / des-referenciação de um ponteiro é responsabilidade do programador. Normalmente, o compilador não dá a mínima, mas o fardo é passado para o programador para gerenciar os ponteiros e garantir que nenhum vazamento de memória ocorra.

Na verdade, em poucas palavras, eles são inicializados no sentido de que, no momento do link, a variável de ponteiro recebe um endereço. Em seu código de exemplo acima, isso com certeza travará ou gerará um SIGSEGV.

Por razões de sanidade, sempre inicialize os ponteiros para NULL, dessa forma, se houver qualquer tentativa de desreferenciá-lo sem mallocou new der uma dica ao programador sobre o motivo pelo qual o programa se comportou mal.

Espero que isso ajude e faça sentido,

t0mm13b
fonte
0

Bem, se C ++ inicializasse os ponteiros, o pessoal de C reclamando "C ++ é mais lento que C" teria algo real para se agarrar;)

Fred
fonte
Esse não é o meu motivo. Minha razão é que se o hardware tem 512 bytes de ROM e 128 bytes de RAM e uma instrução extra para zerar, um ponteiro é mesmo um byte que é uma grande porcentagem de todo o programa. Eu preciso desse byte!
Jerry Jeremiah
0

C ++ vem de um background C - e existem algumas razões que voltam disso:

C, ainda mais que C ++, é uma substituição da linguagem assembly. Ele não faz nada que você não lhe diga para fazer. Portanto: Se você quiser ANULAR - faça!

Além disso, se você anula coisas em uma linguagem bare-metal como C, surgem automaticamente questões de consistência: Se você malhar alguma coisa - ela deve ser zerada automaticamente? Que tal uma estrutura criada na pilha? todos os bytes devem ser zerados? E quanto às variáveis ​​globais? que tal uma declaração como "(* 0x18);" isso não significa que a posição 0x18 da memória deve ser zerada?

gha.st
fonte
Na verdade, em C, se você quiser alocar memória totalmente zero, pode usar calloc().
David Thornley
1
só o que quero dizer - se você quiser fazer isso, você pode, mas não é feito para você automaticamente
gha.st
0

Quais são essas dicas de que você fala?

Para a segurança de exceção, use sempre auto_ptr, shared_ptr, weak_ptre suas outras variantes.
Uma marca registrada de um bom código é aquele que não inclui uma única chamada para delete.

shoosh
fonte
3
Desde C ++ 11, evite auto_ptre substitua unique_ptr.
Deduplicator
-2

Oh garoto. A verdadeira resposta é que é fácil zerar a memória, que é a inicialização básica para, digamos, um ponteiro. O que também não tem nada a ver com a inicialização do próprio objeto.

Considerando os avisos que a maioria dos compiladores dá nos níveis mais altos, não consigo imaginar programar no nível mais alto e tratá-los como erros. Desde que aumentá-los nunca me salvou, mesmo um bug em grandes quantidades de código produzido, não posso recomendar isso.

Queijo Charles Eli
fonte
Se não se espera que o ponteiro seja NULL, inicializá-lo para isso também é um erro.
Deduplicator