¿Cuál es la diferencia real entre punteros y referencias?

AKA – ¿Qué es esta obsesión con los punteros?

Habiendo utilizado realmente modernos lenguajes orientados a objetos como ActionScript, Java y C #, realmente no entiendo la importancia de los punteros y para qué los usa. ¿Qué me estoy perdiendo aquí?

Todo es solo indirección: la capacidad de no tratar con los datos, pero decir “te dirigiré a algunos datos, allá”. Tiene el mismo concepto en Java y C #, pero solo en formato de referencia.

Las diferencias clave son que las referencias son señales efectivamente inmutables, siempre apuntan a algo. Esto es útil y fácil de entender, pero menos flexible que el modelo del puntero C. Los indicadores C son señales que puede reescribir felizmente. ¿Sabes que la cuerda que estás buscando está al lado de la cuerda que se apunta? Bueno, solo modifica ligeramente el poste indicador.

Esto combina bien con el enfoque de C “cercano al hueso, conocimiento de bajo nivel requerido”. Sabemos que un char* foo consiste en un conjunto de caracteres que comienzan en la ubicación señalada por el poste indicador foo. Si también sabemos que la cadena tiene al menos 10 caracteres de longitud, podemos cambiar el poste indicador a (foo + 5) para apuntar a la misma cadena, pero comenzar la mitad de la longitud en.

Esta flexibilidad es útil cuando sabes lo que estás haciendo, y la muerte si no lo haces (donde “saber” es más que solo “conocer el idioma”, es “conocer el estado exacto del progtwig”). Obténgalo mal, y su poste indicador lo llevará por el borde de un acantilado. Las referencias no le permiten tocar el violín, por lo que tiene mucha más confianza en que puede seguirlas sin riesgo (especialmente cuando se combina con reglas como “Un objeto referenciado nunca desaparecerá”, como en la mayoría de los lenguajes recogidos de Basura).

¡Te estás perdiendo mucho! Comprender cómo funciona la computadora en los niveles inferiores es muy útil en varias situaciones. C y el ensamblador lo harán por ti.

Básicamente, un puntero te permite escribir cosas en cualquier punto de la memoria de la computadora. En hardware / SO más primitivo o en sistemas integrados esto realmente podría hacer algo útil. Diga, encienda y apague intermitentemente los blinkenlichts.

Por supuesto, esto no funciona en los sistemas modernos. El sistema operativo es el Señor y Maestro de la memoria principal. Si intenta acceder a una ubicación de memoria incorrecta, su proceso pagará por su arrogancia con su vida útil.

En C, los punteros son la forma de pasar referencias a datos. Cuando llama a una función, no quiere copiar un millón de bits en una stack. En cambio, solo dices dónde están los datos en la memoria principal. En otras palabras, le das un puntero a los datos.

Hasta cierto punto, eso es lo que sucede incluso con Java. Usted pasa referencias a objetos, no a los objetos mismos. Recuerde, en última instancia, cada objeto es un conjunto de bits en la memoria principal de la computadora.

Los punteros son para manipular directamente los contenidos de la memoria.

Depende de usted si cree que esto es algo bueno que hacer, pero es la base de cómo se hace algo en C o en ensamblador.

Los lenguajes de alto nivel ocultan los indicadores detrás de escena: por ejemplo, una referencia en Java se implementa como un puntero en casi cualquier JVM con la que se encuentre, por lo que se llama NullPointerException en lugar de NullReferenceException. Pero no permite que el progtwigdor acceda directamente a la dirección de memoria a la que apunta, y no puede modificarse para tomar un valor que no sea la dirección de un objeto del tipo correcto. Por lo tanto, no ofrece el mismo poder (y responsabilidad) que los punteros en los lenguajes de bajo nivel.

[Editar: esta es una respuesta a la pregunta ‘¿qué es esta obsesión con los punteros?’. Todo lo que he comparado son punteros ensambladores / estilo C con referencias de Java. El título de la pregunta ha cambiado desde entonces: si me hubiera propuesto responder a la nueva pregunta, podría haber mencionado referencias en otros idiomas además de Java]

Esto es como preguntar: “¿Qué es esta obsesión con las instrucciones de la CPU? ¿Me pierdo algo al no rociar las instrucciones MOV x86 por todo el lugar? ”

Solo necesita punteros cuando progtwig en un nivel bajo. En la mayoría de las implementaciones de lenguaje de progtwigción de alto nivel, los punteros se utilizan de forma tan extensa como en C, pero el comstackdor los oculta.

Entonces … No te preocupes. Ya está usando punteros, y sin los peligros de hacerlo incorrectamente también. 🙂

Veo los indicadores como una transmisión manual en un automóvil. Si aprende a conducir con un automóvil que tiene una transmisión automática, eso no representará un mal conductor. Y aún puede hacer casi todo lo que pueden hacer los controladores que aprendió en una transmisión manual. Habrá un agujero en su conocimiento de la conducción. Si tuviera que manejar un manual, probablemente estaría en problemas. Claro, es fácil entender el concepto básico de eso, pero una vez que tienes que hacer una salida en la cima, estás jodido. Pero, todavía hay un lugar para transmisiones manuales. Por ejemplo, los conductores de autos de carrera deben poder cambiar para que el automóvil responda de la manera más óptima a las condiciones actuales de carrera. Tener una transmisión manual es muy importante para su éxito.

Esto es muy similar a la progtwigción en este momento. Existe una necesidad de desarrollo de C / C ++ en algún software. Algunos ejemplos son juegos 3D de alta gama, software incrustado de bajo nivel, donde la velocidad es una parte fundamental del propósito del software, y un lenguaje de nivel inferior que le permite un acceso más cercano a los datos reales que se deben procesar es clave para ese rendimiento . Sin embargo, para la mayoría de los progtwigdores, este no es el caso y no saber los punteros no es paralizante. Sin embargo, creo que todos pueden beneficiarse aprendiendo sobre C y punteros, y también con transmisiones manuales.

Dado que ha estado progtwigndo en lenguajes orientados a objetos, permítame ponerlo de esta manera.

Obtiene el Objeto A instanciando el Objeto B, y lo pasa como un parámetro de método al Objeto C. El Objeto C modifica algunos valores en el Objeto B. Cuando vuelve al código del Objeto A, puede ver el valor modificado en el Objeto B. ¿Por qué esto es tan?

Debido a que pasaste en una referencia del Objeto B al Objeto C, no hiciste otra copia del Objeto B. Entonces el Objeto A y el Objeto C tienen referencias al mismo Objeto B en la memoria. Cambia de un lugar y se ve en otro. Esto se llama por referencia.

Ahora, si usa tipos primitivos, como int o float, y los pasa como parámetros de método, el objeto A no puede ver los cambios en el objeto C, porque el objeto A solo pasa una copia en lugar de una referencia de su propia copia de la variable . Esto se llama Por Valor.

Probablemente ya sabías eso.

Volviendo al lenguaje C, la Función A pasa a la Función B algunas variables. Estos parámetros de función son copias nativas, por valor. Para que la función B manipule la copia que pertenece a la función A, la función A debe pasar un puntero a la variable, de modo que se convierta en un pase por referencia.

“Oye, aquí está la dirección de la memoria de mi variable entera. Pon el nuevo valor en esa dirección y lo retomaré más tarde”.

Tenga en cuenta que el concepto es similar pero no 100% análogo. Los punteros pueden hacer mucho más que simplemente pasar “por referencia”. Los punteros permiten que las funciones manipulen ubicaciones arbitrarias de memoria a cualquier valor requerido. Los punteros también se utilizan para señalar nuevas direcciones de código de ejecución para ejecutar dinámicamente la lógica arbitraria, no solo las variables de datos. Los punteros incluso pueden señalar otros punteros (doble puntero). Eso es poderoso, pero también es bastante fácil introducir errores difíciles de detectar y vulnerabilidades de seguridad.

Si no ha visto punteros anteriormente, seguramente se está perdiendo de esta mini-gem:

 void strcpy(char *dest, char *src) { while(*dest++ = *src++); } 

Históricamente, lo que hizo posible la progtwigción fue la realización de que las ubicaciones de memoria podían contener instrucciones de la computadora, no solo datos.

Los indicadores surgieron al darse cuenta de que las ubicaciones de la memoria también podían contener la dirección de otras ubicaciones de memoria, lo que nos daba una dirección indirecta. Sin punteros (en un nivel bajo) las estructuras de datos más complicadas serían imposibles. Sin listas enlazadas, árboles binarios o tablas hash. No pasa por referencia, solo por valor. Como los punteros pueden apuntar a código, sin ellos tampoco tendríamos funciones virtuales ni tablas de búsqueda de funciones.

Utilizo punteros y referencias en mi trabajo diario … en código administrado (C #, Java) y no administrado (C ++, C). Aprendí sobre cómo manejar los indicadores y lo que son por el propio maestro … [¡Binky!] [1] No se necesita decir nada más;)

La diferencia entre un puntero y una referencia es esto. Un puntero es una dirección a un bloque de memoria. Se puede reescribir o, en otras palabras, reasignar a otro bloque de memoria. Una referencia es simplemente un cambio de nombre de algún objeto. ¡Solo se puede asignar una vez! Una vez que se asigna a un objeto, no se puede asignar a otro. Una referencia no es una dirección, es otro nombre para la variable. Consulte las preguntas frecuentes de C ++ para obtener más información al respecto.

Link1

LInk2

Actualmente me encuentro a la altura de diseñar un software empresarial de alto nivel en el que una o más entidades hacen referencia a fragmentos de datos (almacenados en una base de datos SQL, en este caso). Si queda un trozo de datos cuando no hay más entidades que lo referencian, estamos desperdiciando almacenamiento. Si una referencia apunta a datos que no están presentes, eso también es un gran problema.

Existe una gran analogía entre nuestros problemas y los de gestión de memoria en un lenguaje que utiliza punteros. Es tremendamente útil poder hablar con mis colegas en términos de esa analogía. No borrar datos sin referencia es una “pérdida de memoria”. Una referencia que no lleva a ninguna parte es un “puntero colgante”. Podemos elegir “liberaciones” explícitas, o podemos implementar “recolección de basura” usando “recuento de referencias”.

Entonces, entender la administración de memoria de bajo nivel ayuda a diseñar aplicaciones de alto nivel.

En Java estás usando punteros todo el tiempo. La mayoría de las variables apuntan a los objetos, por lo que:

 StringBuffer x = new StringBuffer("Hello"); StringBuffer y = x; x.append(" boys"); System.out.println(y); 

… imprime “Hola muchachos” y no “Hola”.

La única diferencia en C es que es común sumr y restar de punteros, y si te equivocas con la lógica, puedes terminar jugando con datos que no debes tocar.

Las cadenas son fundamentales para C (y otros lenguajes relacionados). Cuando programe en C, debe administrar su memoria. No solo dices “está bien, necesitaré muchas cuerdas”; debes pensar en la estructura de datos. Cuanta memoria necesitas? ¿Cuándo lo asignarás? ¿Cuándo lo liberarás? Digamos que quieres 10 cadenas, cada una con no más de 80 caracteres.

De acuerdo, cada cadena es una matriz de caracteres (81 caracteres, ¡no debes olvidar el nulo o lo lamentarás!) Y luego cada cadena se encuentra en una matriz. El resultado final será una matriz multidimensional algo así como

 char dict[10][81]; 

Tenga en cuenta, por cierto, que dict no es una “cadena” o una “matriz”, o un “char”. Es un puntero. Cuando intenta imprimir una de esas cadenas, todo lo que hace es pasar la dirección de un solo carácter; C asume que si solo comienza a imprimir caracteres eventualmente golpeará un nulo. Y supone que si está al comienzo de una cadena y avanza 81 bytes, estará al comienzo de la siguiente cadena. Y, de hecho, tomar el puntero y agregarle 81 bytes es la única forma posible de pasar a la siguiente cadena.

Entonces, ¿por qué son importantes los punteros? Porque no puedes hacer nada sin ellos. Ni siquiera puedes hacer algo simple como imprimir un montón de hilos; Ciertamente no puedes hacer nada interesante como implementar listas enlazadas, o hashes, o colas, o árboles, o un sistema de archivos, o algún código de administración de memoria, o un kernel o … lo que sea. NECESITAS entenderlos porque C solo te entrega un bloque de memoria y deja que hagas el rest, y hacer cualquier cosa con un bloque de memoria en bruto requiere punteros.

También muchas personas sugieren que la capacidad de entender los indicadores se correlaciona altamente con la habilidad de progtwigción. Joel ha hecho este argumento, entre otros. Por ejemplo

Ahora, reconozco abiertamente que la progtwigción con punteros no es necesaria en el 90% del código escrito hoy, y de hecho, es francamente peligroso en el código de producción. DE ACUERDO. Esta bien. Y la progtwigción funcional simplemente no se usa mucho en la práctica. Convenido.

Pero sigue siendo importante para algunos de los trabajos de progtwigción más interesantes. Sin punteros, por ejemplo, nunca sería capaz de trabajar en el kernel de Linux. No puede entender una línea de código en Linux, o, de hecho, cualquier sistema operativo, sin entender realmente los punteros.

Desde aquí . Excelente artículo.

Para ser honesto, la mayoría de los desarrolladores experimentados se reirán (con suerte, amigables) si no conoce los punteros. En mi anterior empleo tuvimos dos nuevos empleados el año pasado (recién graduados) que no sabían sobre punteros, y solo ese fue el tema de conversación con ellos durante una semana. Nadie podía creer cómo alguien podría graduarse sin saber los consejos …

Las referencias en C ++ son fundamentalmente diferentes de las referencias en Java o lenguajes .NET; Los lenguajes .NET tienen tipos especiales llamados “byrefs” que se comportan de manera similar a las “referencias” de C ++.

Una referencia C ++ o byref .NET (usaré el último término, para distinguir de las referencias .NET) es un tipo especial que no contiene una variable, sino que contiene información suficiente para identificar una variable (o algo que puede comportarse) como uno, como una ranura de matriz) en otro lugar. Por lo general, los byrefs solo se usan como parámetros / argumentos de funciones y están destinados a ser efímeros. El código que transfiere un byref a una función garantiza que la variable que se identifique existirá al menos hasta que esa función regrese, y las funciones generalmente garantizan que no se guardará ninguna copia de un byref después de su retorno (obsérvese que en C ++ la última restricción no es forzado). Por lo tanto, las referencias no pueden sobrevivir a las variables identificadas de ese modo.

En los lenguajes Java y .NET, una referencia es un tipo que identifica un objeto Heap; cada objeto de montón tiene una clase asociada, y el código en la clase del objeto de montón puede acceder a los datos almacenados en el objeto. Los objetos Heap pueden otorgar acceso externo limitado al código o acceso total a los datos almacenados en el mismo, y / o permitir que el código externo llame a ciertos métodos dentro de su clase. El uso de una referencia para llamar a un método de su clase hará que esa referencia esté disponible para ese método, que luego puede usarla para acceder a datos (incluso datos privados) dentro del objeto Heap.

Lo que hace que las referencias sean especiales en los lenguajes Java y .NET es que mantienen, como una invariante absoluta, que cada referencia no nula continuará identificando el mismo objeto de montón siempre que esa referencia exista. Una vez que no existe ninguna referencia a un objeto Heap en cualquier parte del universo, el objeto Heap simplemente dejará de existir, pero no hay forma de que un objeto Heap pueda dejar de existir mientras que exista alguna referencia a él, ni existe ninguna forma de una “normalidad”. “referencia a un objeto de montón para convertirse espontáneamente en algo más que una referencia a ese objeto. Tanto Java como .NET tienen tipos especiales de “referencia débil”, pero incluso ellos mantienen el invariante. Si no existen referencias no débiles a un objeto en cualquier parte del universo, cualquier referencia débil existente será invalidada; una vez que eso ocurre, no habrá ninguna referencia al objeto y, por lo tanto, puede invalidarse.

Los punteros, como las referencias de C ++ y las referencias de Java / .NET, identifican los objetos, pero a diferencia de los tipos de referencias mencionados anteriormente, pueden sobrevivir a los objetos que identifican. Si el objeto identificado por un puntero deja de existir pero el puntero no lo hace, cualquier bash de utilizar el puntero dará como resultado un comportamiento indefinido. Si no se conoce que un puntero sea null o que identifique un objeto que existe actualmente, no existe una forma definida por el estándar para hacer algo con ese puntero que no sea sobrescribirlo con otra cosa. Es perfectamente legítimo que un puntero continúe existiendo después de que el objeto identificado haya dejado de hacerlo, siempre que nada use el puntero, pero es necesario que algo fuera del puntero indique si es seguro usarlo o no, porque no hay forma de hacerlo. pregunte el puntero mismo.

La diferencia clave entre los punteros y las referencias (de cualquier tipo) es que las referencias siempre se pueden preguntar si son válidas (serán válidas o identificables como nulas), y si se observa que son válidas, permanecerán siempre que existe. No se puede preguntar a los punteros si son válidos, y el sistema no hará nada para garantizar que los punteros no se vuelvan inválidos, ni permitirá que los punteros que se vuelven inválidos se reconozcan como tales.

Durante mucho tiempo no entendí los punteros, pero entendí el direccionamiento de arreglos. Por lo tanto, normalmente armaba un área de almacenamiento para objetos en una matriz y luego utilizaba un índice para esa matriz como concepto de “puntero”.

 SomeObject store[100]; int a_ptr = 20; SomeObject A = store[a_ptr]; 

Un problema con este enfoque es que después de modificar ‘A’, tendría que reasignarlo a la matriz ‘almacenar’ para que los cambios sean permanentes:

 store[a_ptr] = A; 

Detrás de escena, el lenguaje de progtwigción estaba haciendo varias operaciones de copia. La mayoría de las veces esto no afectó el rendimiento. En su mayoría hizo que el código sea propenso a errores y repetitivo.

Después de aprender a entender los indicadores, me alejé de implementar el enfoque de direccionamiento de matriz. La analogía sigue siendo bastante válida. Solo tenga en cuenta que la matriz de “tienda” se gestiona mediante el tiempo de ejecución del lenguaje de progtwigción.

 SomeObject A; SomeObject* a_ptr = &A; // Any changes to a_ptr's contents hereafter will affect // the one-true-object that it addresses. No need to reassign. 

Hoy en día, solo uso punteros cuando no puedo copiar un objeto legítimamente. Hay un montón de razones por las cuales este podría ser el caso:

  1. Para evitar una costosa operación de copia de objetos por el bien del rendimiento.
  2. Algún otro factor no permite una operación de copia de objeto.
  3. Desea una llamada de función para tener efectos secundarios en un objeto (no pase el objeto, pase el puntero al mismo).
  4. En algunos idiomas, si desea devolver más de un valor de una función (aunque generalmente se evita).

Los punteros son la forma más pragmática de representar la indirección en los lenguajes de progtwigción de bajo nivel.

¡Los indicadores son importantes! Ellos “apuntan” a una dirección de memoria, y muchas estructuras internas se representan como punteros, IE, ¡Una matriz de cadenas es en realidad una lista de punteros a punteros! Los punteros también se pueden usar para actualizar las variables pasadas a las funciones.

Los necesita si desea generar “objetos” en el tiempo de ejecución sin preasignar memoria en la stack

Eficiencia del parámetro: pasar un puntero (Int – 4 bytes) en lugar de copiar un objeto entero (arbitarmente grande).

Las clases de Java se pasan por referencia (básicamente un puntero) también por cierto, es solo eso en Java que está oculto para el progtwigdor.

Progtwigndo en idiomas como C y C ++ estás mucho más cerca del “metal”. Los punteros tienen una ubicación de memoria donde viven sus variables, datos, funciones, etc. Puede pasar un puntero en lugar de pasar por valor (copiando sus variables y datos).

Hay dos cosas que son difíciles con punteros:

  1. Los punteros en punteros, direcciones, etc. pueden volverse muy crípticos. Lleva a errores y es difícil de leer.
  2. La memoria a la que apuntan los punteros a menudo se asigna desde el montón, lo que significa que usted es responsable de liberar esa memoria. Cuanto más grande sea tu aplicación, más difícil será cumplir este requisito, y terminarás con memory leaks que son difíciles de rastrear.

Puede comparar el comportamiento del puntero con la forma en que se pasan los objetos Java, con la excepción de que en Java no tiene que preocuparse de liberar la memoria, ya que la recolección de basura la maneja. De esta forma obtienes lo bueno de los punteros pero no tienes que lidiar con los negativos. Todavía puede obtener pérdidas de memoria en Java, por supuesto, si no elimina la referencia de los objetos, pero ese es un asunto diferente.

También algo a tener en cuenta, puede utilizar punteros en C # (a diferencia de las referencias normales) marcando un bloque de código como inseguro. A continuación, puede ejecutar el cambio de direcciones de memoria directamente y hacer aritmética de puntero y todas esas cosas divertidas. Es ideal para la manipulación de imágenes muy rápida (el único lugar donde personalmente lo he usado).

Por lo que sé, Java y ActionScript no admiten códigos y punteros inseguros.

Siempre estoy angustiado por el enfoque en cosas tales como punteros o referencias en lenguajes de alto nivel. Es realmente útil pensar en un nivel superior de abstracción en términos del comportamiento de los objetos (o incluso de las funciones) en lugar de pensar en términos de “déjame ver, si le envío la dirección de esta cosa, entonces esa cosa me devolverá un puntero a otra cosa ”

Considere incluso una función de intercambio simple. Si usted tiene

void swap (int & a, int & b)

o

procedimiento Swap (var a, b: integer)

entonces interprete estos para significar que los valores pueden ser cambiados. El hecho de que esto se implemente pasando las direcciones de las variables es solo una distracción del propósito.

Lo mismo con los objetos — no piense en los identificadores de objetos como punteros o referencias a “cosas”. En cambio, solo piensa en ellos como, bueno, OBJETOS, a los que puedes enviar mensajes. Incluso en lenguajes primitivos como C ++, puede ir mucho más rápido pensando (y escribiendo) en el nivel más alto posible.

Escribe más de 2 líneas de c o c ++ y lo descubrirás.

Son “punteros” a la ubicación de la memoria de una variable. Es como pasar una variable por referencia un poco.