¿Por qué los punteros no se inicializan con NULL de forma predeterminada?

118

¿Alguien puede explicar por qué los punteros no se inicializan en NULL?
Ejemplo:

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

El programa no entraría en el if porque bufno es nulo.

Me gustaría saber por qué, en qué caso necesitamos una variable con basura activada, especialmente punteros que se dirijan a la basura en la memoria.

Jonathan
fuente
13
Bueno, porque los tipos fundamentales se dejan sin inicializar. Así que supongo que su pregunta "real" es: ¿por qué no se inicializan los tipos fundamentales?
GManNickG
11
"el programa no entraría en el if porque buf no es nulo". Eso no es correcto. Como no sabe qué es buf , no puede saber qué no es .
Drew Dormann
A diferencia de algo como Java, C ++ otorga mucha más responsabilidad al desarrollador.
Rishi
enteros, punteros, por defecto a 0 si usa el constructor ().
Erik Aronesty
Debido a la suposición de que alguien que usa C ++ sabe lo que está haciendo, además, alguien que usa punteros en bruto sobre un puntero inteligente sabe (aún más) lo que está haciendo.
Lofty Lion

Respuestas:

161

Todos sabemos que el puntero (y otros tipos de POD) deben inicializarse.
La pregunta entonces es 'quién debería inicializarlos'.

Bueno, básicamente hay dos métodos:

  • El compilador los inicializa.
  • El desarrollador los inicializa.

Supongamos que el compilador inicializó cualquier variable no inicializada explícitamente por el desarrollador. Luego nos encontramos con situaciones en las que inicializar la variable no era trivial y la razón por la que el desarrollador no lo hizo en el punto de declaración fue que necesitaba realizar alguna operación y luego asignar.

Entonces ahora tenemos la situación en la que el compilador ha agregado una instrucción adicional al código que inicializa la variable a NULL y luego se agrega el código del desarrollador para realizar la inicialización correcta. O bajo otras condiciones, la variable potencialmente nunca se usa. Muchos desarrolladores de C ++ gritarían mal en ambas condiciones a costa de esa instrucción adicional.

No es solo cuestión de tiempo. Pero también espacio. Hay muchos entornos en los que ambos recursos son un bien escaso y los desarrolladores tampoco quieren renunciar.

PERO : Puede simular el efecto de forzar la inicialización. La mayoría de los compiladores le advertirán sobre las variables no inicializadas. Así que siempre cambio mi nivel de advertencia al nivel más alto posible. Luego dígale al compilador que trate todas las advertencias como errores. En estas condiciones, la mayoría de los compiladores generarán un error para las variables que no están inicializadas pero que se usan y, por lo tanto, evitarán que se genere código.

Martin York
fuente
5
Bob Tabor dijo: “¡Demasiadas personas no han pensado lo suficiente en la inicialización!” Es 'amigable' inicializar todas las variables automáticamente, pero lleva tiempo y los programas lentos son 'poco amigables'. Una hoja de cálculo o editores que mostraran la basura aleatoria malloc encontrada serían inaceptables. C, una herramienta afilada para usuarios capacitados (peligrosa si se usa incorrectamente) no debería tomar tiempo inicializar variables automáticas. Podría ser una macro de rueda de entrenamiento para las variables de inicio, pero muchos piensan que es mejor ponerse de pie, ser consciente y sangrar un poco. En caso de apuro, trabajas como practicas. Así que practica como quieras.
Proyecto de ley IV
2
Se sorprendería de la cantidad de errores que se evitarían simplemente si alguien corrigiera toda su inicialización. Este sería un trabajo tedioso si no fuera por las advertencias del compilador.
Jonathan Henson
4
@Loki, estoy teniendo dificultades para seguir tu punto. Solo estaba tratando de elogiar su respuesta como útil, espero que lo haya entendido. Si no, lo siento.
Jonathan Henson
3
Si el puntero se establece primero en NULL y luego se establece en cualquier valor, el compilador debería poder detectar esto y optimizar la primera inicialización NULL, ¿verdad?
Korchkidu
1
@Korchkidu: A veces. Sin embargo, uno de los principales problemas es que no hay forma de que le advierta que olvidó hacer su inicialización, ya que no puede saber que el valor predeterminado no es perfecto para su uso.
Deduplicador
41

Citando a Bjarne Stroustrup en TC ++ PL (Edición especial p.22):

La implementación de una función no debe imponer gastos generales importantes a los programas que no la requieran.

Juan
fuente
y tampoco des la opción. Parece
Jonathan
8
@ Jonathan nada le impide inicializar el puntero en nulo, o en 0, como es estándar en C ++.
stefanB
8
Sí, pero Stroustrup podría haber hecho que la sintaxis predeterminada favoreciera la corrección del programa en lugar del rendimiento al inicializar el puntero en cero, y hacer que el programador tenga que solicitar explícitamente que el puntero no se inicialice. Después de todo, la mayoría de la gente prefiere lo correcto pero lento a lo rápido pero incorrecto, debido a que generalmente es más fácil optimizar una pequeña cantidad de código que corregir errores en todo el programa. Especialmente cuando gran parte de esto puede ser realizado por un compilador decente.
Robert Tuck
1
No rompe la compatibilidad. La idea se ha considerado junto con "int * x = __uninitialized": seguridad por defecto, velocidad por intención.
MSalters
4
Me gusta lo que Dhace. Si no desea la inicialización, utilice esta sintaxis float f = void;o int* ptr = void;. Ahora está inicializado de forma predeterminada, pero si realmente lo necesita, puede evitar que el compilador lo haga.
deft_code
23

Porque la inicialización lleva tiempo. Y en C ++, lo primero que debe hacer con cualquier variable es inicializarla explícitamente:

int * p = & some_int;

o:

int * p = 0;

o:

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

fuente
1
k, si la inicialización lleva tiempo y aún lo quiero, ¿hay alguna forma de hacer que mis punteros se vuelvan nulos sin configurarlos manualmente? mira, no porque no quiera corregirlo, porque parece que nunca jamás usaré punteros unitiliazed con basura en su dirección
Jonathan
1
Inicializa los miembros de la clase en el constructor de la clase; así es como funciona C ++.
3
@Jonathan: pero null también es basura. No puede hacer nada útil con un puntero nulo. Desreferenciar uno es igualmente un error. Cree punteros con valores adecuados, no nulos.
DrPizza
2
Inicializar un puntero en Nnull puede ser algo sensato, y hay varias operaciones que puede realizar en punteros nulos: puede probarlos y puede llamar a delete en ellos.
4
Si nunca va a usar un puntero sin inicializarlo explícitamente, no importa lo que contenía antes de darle un valor, y según el principio de C y C ++ de pagar solo por lo que usa, no está hecho automáticamente. Si hay un valor predeterminado aceptable (generalmente el puntero nulo), debe inicializarlo. Puede inicializarlo o dejarlo sin inicializar, a su elección.
David Thornley
20

Porque uno de los lemas de C ++ es:


No pagas por lo que no usas


Por esta misma razón, el operator[]de la vectorclase no comprueba si el índice está fuera de los límites, por ejemplo.

KeatsPeeks
fuente
12

Por razones históricas, principalmente porque así es como se hace en C. Por qué se hace así en C, es otra cuestión, pero creo que el principio de cero gastos generales estuvo involucrado de alguna manera en esta decisión de diseño.

AraK
fuente
Supongo que porque C se considera un lenguaje de nivel inferior con fácil acceso a la memoria (también conocidos como punteros), por lo que le da libertad para hacer lo que quiera y no impone una sobrecarga al inicializar todo. Por cierto, creo que depende de la plataforma porque trabajé en una plataforma móvil basada en Linux que inicializó toda su memoria a 0 antes de usarla, por lo que todas las variables se establecerían en 0.
stefanB
8

Además, tenemos una advertencia para cuando lo explote: "posiblemente se use antes de que se le asigne un valor" o verbage similar dependiendo de su compilador.

Compila con advertencias, ¿verdad?

Joshua
fuente
Y es posible como reconocimiento de que el rastreo de los compiladores puede ser defectuoso.
Deduplicador
6

Hay muy pocas situaciones en las que alguna vez tenga sentido que una variable no esté inicializada, y la inicialización predeterminada tiene un costo pequeño, entonces, ¿por qué hacerlo?

C ++ no es C89. Demonios, incluso C no es C89. Puede mezclar declaraciones y código, por lo que debe posponer la declaración hasta el momento en que tenga un valor adecuado para inicializar.

DrPizza
fuente
2
Entonces, cada valor deberá escribirse dos veces: una mediante la rutina de configuración del compilador y otra vez mediante el programa del usuario. No suele ser un gran problema, pero se acumula (por ejemplo, si está creando una matriz de 1 millón de elementos). Si desea una inicialización automática, siempre puede crear sus propios tipos que lo hagan; pero de esta manera no está obligado a aceptar gastos generales innecesarios si no lo desea.
Jeremy Friesner
3

Un puntero es solo otro tipo. Si crea un tipo de POD int, charo cualquier otro, no se inicializa a cero, entonces, ¿por qué debería hacerlo un puntero? Esto podría considerarse una sobrecarga innecesaria para alguien que escribe un programa como este.

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

Si sabe que lo va a inicializar, ¿por qué debería incurrir en un costo el programa cuando crea por primera vez pBufen la parte superior del método? Este es el principio de cero gastos generales.

Leopardopielpíldoracajasombrero
fuente
1
por otro lado, podría hacer char * pBuf = condition? nuevo carácter [50]: m_myMember-> buf (); Es más como una cuestión de sintaxis que de eficiencia, pero estoy de acuerdo contigo de todos modos.
the_drow
1
@the_drow: Bueno, uno puede hacerlo más complejo solo para que tal reescritura no sea posible.
Deduplicador
2

Si desea un puntero que siempre se inicialice en NULL, puede usar una plantilla de C ++ para emular esa funcionalidad:

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
fuente
1
Si estuviera implementando esto, no me molestaría con el copy ctor o la operación de asignación; los valores predeterminados están bastante bien. Y tu destructor es inútil. Por supuesto, también puede probar los punteros utilizando menos que operater et all) en algunas circunstancias), por lo que debe proporcionarlos.
Bien, menos de lo que es trivial de implementar. Tenía el destructor para que si el objeto se sale del alcance (es decir, se define localmente dentro de un subámbito de una función) pero todavía ocupa espacio en la pila, la memoria no se deja como un puntero colgante a la basura. Pero amigo, en serio, escribí esto en menos de 5 minutos. No está destinado a ser perfecto.
Adisak
OK agregó todos los operadores de comparación. Las anulaciones predeterminadas pueden ser redundantes, pero están aquí explícitamente, ya que este es un ejemplo.
Adisak
1
No pude entender cómo esto haría que todos los punteros fueran nulos sin configurarlos manualmente, ¿podría explicar lo que hizo aquí, por favor?
Jonathan
1
@Jonathan: Se trata básicamente de un "puntero inteligente" que no hace más que establecer el puntero en nulo. IE en lugar de Foo *ausar InitializedPointer<Foo> a: un ejercicio puramente académico, ya que Foo *a=0es menos mecanografía. Sin embargo, el código anterior es muy útil desde un punto de vista educativo. Con una pequeña modificación (al ctor / dtor de "mantenimiento de posición" y las operaciones de asignación), podría extenderse fácilmente a varios tipos de punteros inteligentes, incluidos los punteros con alcance (que se liberan en el destructor) y los punteros contados de referencia agregando inc / dec operaciones cuando m_pPointer se establece o borra.
Adisak
2

Tenga en cuenta que los datos estáticos se inicializan en 0 (a menos que diga lo contrario).

Y sí, siempre debes declarar tus variables lo más tarde posible y con un valor inicial. Código como

int j;
char *foo;

debería hacer sonar las alarmas cuando lo lea. Sin embargo , no sé si se puede persuadir a alguna pelusa para que se queje, ya que es 100% legal.

pm100
fuente
¿Eso está GARANTIZADO o simplemente es una práctica común utilizada por los compiladores actuales?
gha.st
1
las variables estáticas se inicializan en 0, lo que también hace lo correcto para los punteros (es decir, los establece en NULL, no todos los bits 0). Este comportamiento está garantizado por el estándar.
Alok Singhal
1
La inicialización de datos estáticos a cero está garantizada por el estándar C y C ++, no es solo una práctica común
groovingandi
1
¿Quizás porque algunas personas quieren asegurarse de que su pila esté bien alineada, pre-declaran todas las variables en la parte superior de la función? ¿Quizás están escribiendo en un dialecto que REQUIERE esto?
KitsuneYMG
1

Otra posible razón por la cual, es que en el momento del enlace, los punteros reciben una dirección, pero el direccionamiento / desreferenciación indirecto de un puntero es responsabilidad del programador. Por lo general, al compilador no le importa menos, pero la carga se pasa al programador para administrar los punteros y asegurarse de que no se produzcan pérdidas de memoria.

Realmente, en pocas palabras, se inicializan en el sentido de que en el momento del enlace se le da una dirección a la variable de puntero. En su código de ejemplo anterior, se garantiza que se bloqueará o generará un SIGSEGV.

En aras de la cordura, siempre inicialice los punteros a NULL, de esa manera si algún intento de desreferenciarlo sin malloco newindicará al programador la razón por la que el programa se comportó mal.

Espero que esto ayude y tenga sentido,

t0mm13b
fuente
0

Bueno, si C ++ inicializara los punteros, entonces la gente de C que se queja de "C ++ es más lento que C" tendría algo real a lo que aferrarse;)

Fred
fuente
Ésa no es mi razón. Mi razón es que si el hardware tiene 512 bytes de ROM y 128 bytes de RAM y una instrucción adicional para poner a cero un puntero es incluso un byte, que es un porcentaje bastante grande de todo el programa. ¡Necesito ese byte!
Jerry Jeremiah
0

C ++ proviene de un entorno C, y hay algunas razones que regresan de esto:

C, incluso más que C ++, es un reemplazo del lenguaje ensamblador. No hace nada que no le digas que haga. Por lo tanto: si desea NULARlo, ¡hágalo!

Además, si anula cosas en un lenguaje bare-metal como C, surgen automáticamente preguntas de coherencia: si mallociza algo, ¿debería ponerse a cero automáticamente? ¿Qué pasa con una estructura creada en la pila? ¿Deberían ponerse a cero todos los bytes? ¿Qué pasa con las variables globales? ¿qué pasa con una declaración como "(* 0x18);" ¿No significa eso entonces que la posición de memoria 0x18 debería ponerse a cero?

gha.st
fuente
En realidad, en C, si desea asignar memoria totalmente cero, puede usar calloc().
David Thornley
1
solo mi punto - si quieres hacerlo, puedes, pero no se hace
automáticamente
0

¿Cuáles son estos indicadores de los que habla?

Para la seguridad de excepción, siempre use auto_ptr, shared_ptr, weak_ptry sus otras variantes.
Un sello distintivo de un buen código es aquel que no incluye una sola llamada a delete.

shoosh
fuente
3
Desde C ++ 11, evite auto_ptry sustituya unique_ptr.
Desduplicador
-2

Oh chico. La respuesta real es que es fácil poner a cero la memoria, que es la inicialización básica para, por ejemplo, un puntero. Lo que tampoco tiene nada que ver con la inicialización del objeto en sí.

Teniendo en cuenta las advertencias que la mayoría de los compiladores dan en los niveles más altos, no puedo imaginarme programando al nivel más alto y tratándolos como errores. Dado que subirlos nunca me ha salvado ni siquiera un error en grandes cantidades de código producido, no puedo recomendar esto.

Queso Charles Eli
fuente
Si no se espera que el puntero lo esté NULL, inicializarlo es un error.
Deduplicador