¿Qué pasa con los punteros en C confunde a las personas?

Mientras estaba en la universidad, fui voluntario en una sala de ayuda para estudiantes de primer año y segundo año de CprE / EE. Estos estudiantes tuvieron problemas con C, y generalmente carecían de una comprensión fundamental de muchos conceptos de programación.

Si un estudiante no entendía los punteros, siempre comenzaría midiendo el conocimiento de las variables. La mayoría de las personas al menos entendieron cómo usar variables (si no, tendían a abandonar). Entramos en más detalles sobre qué es realmente una variable y dónde puede almacenarse en la memoria. Continuaría con un par de ejemplos sobre el uso de * y & para asignar un nuevo valor a algo. Finalmente, haríamos un breve ejemplo de cambiar el valor de una variable externa dentro de una función. Las matrices y las operaciones de puntero en las matrices también fueron una buena discusión, pero generalmente demasiado para alguien que no comprende los fundamentos.

El núcleo del problema generalmente era que el estudiante no estaba pensando en programar de la manera correcta. Intentaban ir demasiado rápido. Intentaban aplicar la memorización de memoria a la programación, en lugar de deconstruir lo que realmente estaba sucediendo.

Los punteros en C son una tormenta perfecta. La energía de la complejidad derivada de muchos orígenes se encuentra en un solo punto. Después de que apenas logras escapar del estrecho estrecho de sintaxis, esta energía te estrella en las rocas de la corrupción de la memoria o se ahoga en tu nave con fugas.

Estas son las energías que amenazan al marinero de las altas C:

[1] Navegando por los punteros
Toda la noción de punteros es confusa. De hecho, tuve un compañero de trabajo que había programado en C ++ antes, se confundió con los punteros de Python. “Pero Python no tiene punteros “. Le han mentido, ya que Python tiene punteros , y también Java.

def byRef (arr):
arr [:] = [1]
arr = [2]
print (‘en byRef = {}’. formato (arr))

a = [1,2,3]
por Ref (a)
print (‘after byRef = {}’. format (a))

Ejecutar esto confundirá a muchos programadores de Python con la salida:

en byRef = [2]
después de byRef = [1]

¿De dónde, en nombre del poderoso Kraken, viene esto? ¿Por qué ‘ a’ cambió en absoluto? ¿Y por qué obtuvo [1] , que fue asignado primero, pero no obtuvo [2] , que fue segundo?

El faro de la asamblea. saber hardware y ensamblaje ayuda aquí, ya que comienzas a entender que los punteros son direcciones. Usted comprende que arr es solo un puntero al objeto de matriz, y [math] arr [:] = [1] [/ math] no cambia el puntero, sino que va directamente al objeto. Entonces verá que arr = [2] reemplaza el puntero que ‘ arr ‘ contiene y no toca el objeto original.

[2] El laberinto de la recursividad
Cuando era joven, vi el faro desde lejos; Tenía mis flechas de montaje en mi carcaj. Pero el Minotauro logró atraparme en su reino de datos recursivos. Me tomó algo de tiempo escapar de este pequeño pedazo de Laberinto:

struct tree_node {
datos int;
struct tree_node * left, * right;
struct tree_node * parent;
};

¿Dónde está el principio y el final de esto? Para entender qué tree_node es, lee sus campos, pero luego gira a la izquierda y vuelve al principio. Gira a la derecha , pero eso no te lleva a ninguna parte. Este laberinto no tiene fin, y ni siquiera puedo encontrar el comienzo. Tuve que encontrar un hilo de pensamiento en este desastre: algunas habilidades básicas para navegar por la recursión.

Esto no es específico de este C, y también afectará a cualquiera que navegue a Java. Pero las sirenas del C confunden tu mente más de lo que tienen que enfrentar los marineros de Java. ¿Cómo se produce un conocimiento de embarque, para tree_node, cuando tree_node se contiene a sí mismo?

Serás como un ciervo atrapado en los faros hasta que veas el faro de la asamblea. Los punteros son solo bits en la memoria, y el compilador no necesita saber a qué apuntan exactamente izquierda y derecha , solo que son flechas en su mapa de memoria. E incluso después de comprender eso, aún debe comprender la recursividad.

[3] El estrecho de sintaxis
La C de la sintaxis es muy confusa, y una simple mirada en sus ojos te petrificará:

int * ptr; // * significa definición
int a = * ptr; // * significa leer datos
* ptr = a; // * significa escribir datos!
ptr = & a; // & significa tomar un puntero!?

La sintaxis no es muy consistente. Además, esta es la única forma de pasar valores por referencia a funciones. Estos pequeños dardos de punteros lo atraparán si desea obtener más de un bit de información del oráculo de funciones. El oráculo te hablará solo si traes el sacrificio de int* y double const*const* , o (horrores) int**(*)(int *) .

[4] Poseidón te estrellará contra las rocas
Además de todo, si mallocas () el tamaño incorrecto, a veces chocas, y otras no, por los caprichos de Moira y el señor del mar. Si olvida inicializar el puntero, Hades se alegrará de obtener su alma. Si señala dónde no debería, Hermes le robará su oro. En el mejor de los casos, un centinela pensará que estás tratando de violar los derechos de su monarca OS y disparará una flecha para matar.

[5] Un barco con fugas se hunde

Habiendo visto tantas aventuras, aún no estás seguro. Su bote está goteando como un tamiz. Debes tirar toda el agua que hayas bombeado, de lo contrario tu pesado barco se hundirá. A diferencia de C, C ++ no le permite hundirse tan rápido y las aguas de Java son más ligeras, pero las naves aún se escapan a través de sus grietas de devolución de llamada.


Los punteros son difíciles porque se los aborda como una nuez monolítica grande para romper. Es muy difícil separar todos los aspectos de la dificultad en pequeños fragmentos. Si, en lugar de C, comienza a aprender el ensamblaje Python o Java, puede escapar del estrecho de la sintaxis, pero no los otros problemas.

En mi opinión, las estructuras recursivas son tanto el pináculo del uso del puntero como la parte más difícil de comprender. Sin estructuras recursivas, realmente no puedes entender los punteros. Afortunadamente, puede sumergirse en el laberinto de estructuras recursivas en las aguas de Java o en las tierras de la gran Python, sin navegar por los traicioneros estrechos de la sintaxis de puntero y la gestión de la memoria de la C.


Cómo simplificar los punteros de aprendizaje:

  1. Aprenda el ensamblaje o, al menos, cómo se organiza la memoria. Aprenda bits y bytes, y las direcciones de estos bytes.
  2. Aprenda estructuras de datos recursivas en lenguajes administrados como Python o Java.

Después de hacer 1 y 2, comprender los punteros debería ser relativamente fácil, ya que todo lo que le queda por aprender es la sintaxis [3], la validez de los punteros [4] y las pérdidas de memoria [5]. Estas partes son mucho más fáciles de entender cuando no se entremezclan con la noción de punteros [1] y el concepto de datos recursivos estructurados [2].

En mi opinión, se introducen demasiado tarde en la programación curricular.

Por lo general, en un plan de estudios moderno de programación introductoria, el enfoque se centra en las estructuras de control, las funciones y una noción muy abstracta de una variable. La noción de punteros (o referencias) llega mucho más tarde, después de que el estudiante ya ha construido un modelo mental que excluye tales conceptos.

El alumno sabe que puede declarar una variable y que puede contener algún valor, por ejemplo, un número entero, suponiendo que incluso estamos usando un lenguaje escrito. Más tarde, se introduce el concepto de puntero o referencia, quizás en el contexto de un nuevo lenguaje, y la estudiante, que está acostumbrada a pensar en términos de variables que simplemente “tienen un valor”, debe incorporar ahora a su modelo mental el noción de direcciones de memoria, la contigüidad de esas direcciones y posibles nociones como paso por valor y paso por referencia, que no son triviales y confusas para muchos estudiantes.

Prefiero introducir la noción de memoria y direcciones de memoria tan pronto como introduzcamos variables . Al principio, se necesita un poco más de tiempo y esfuerzo mental, pero el estudiante comienza a desarrollar las herramientas mentales para pensar sobre lo que la computadora está haciendo realmente, y está mejor equipado para lidiar con conceptos como la aritmética de punteros más adelante.

La semántica de referencia de Java tiene sus peculiaridades (inmutabilidad, paso de valores de referencia), y C ++ usa el operador * para definir punteros y desreferenciar. Los lenguajes como Python, que ocultan incluso el tipeo de las variables, crean una situación en la que un estudiante principiante necesitará aún más refactorización mental más adelante.

El puntero es algo que puede apuntar a otros tipos de datos o que puede almacenar la dirección de otras variables. Esta es una declaración simple.

Pero cuando trabajemos con el puntero, encontraremos dificultades porque, para entenderlo, debes ser muy bueno en la imaginación.

Estas son las cosas que debe hacer para comprenderlo correctamente.

  1. Dibuja un diagrama de memoria.
  2. Dé la dirección según el tipo de datos.

muchos estudiantes se confundirán cuando tengamos que correlacionar matrices con punteros. Muchos estudiantes no saben que un nombre de matriz solo representa su dirección. consulte el siguiente ejemplo.

int arr [5];
int * ptr;
ptr = arr;

Aquí ptr es un puntero entero que apunta a la dirección inicial de la matriz “arr”.

Entonces, si imprime ptr, ptr + 1, ptr + 2 … Imprimirá 1A02,1A04,1A06 … respectivamente. que será lo mismo que & arr [0], & arr [1], & arr [2] ..

Una vez que comprenda las cosas anteriores, puede ir con otras cosas como a continuación

  1. ¿Cuál será el valor de * ptr?
  2. ¿Cuál será el valor de * (ptr + 1)?
  3. ¿Cuál será el valor de (* p) +3?

Escriba un programa simple e intente encontrar las respuestas anteriores e intente comprender cómo funciona.

Para ser bueno en el puntero, uno debe comprender diferentes cosas como a continuación:

  1. correlacionan una matriz de caracteres con un puntero entero.
  2. entender doble puntero.
  3. Comprender el puntero a diferentes tipos de datos como estructura, función, etc.
  4. Comprender la suma y resta de punteros.

Lea un buen libro para entender el puntero por completo.

Los punteros y la gestión de la memoria como concepto a menudo se enseñan horriblemente, en todo caso.

He estado en muchas clases de informática. No recuerdo que haya una sola que proporcione una explicación detallada y detallada de cómo funciona la memoria, qué sucede cuando declaras una variable, cuáles son la pila y el montón, la asignación estática frente a la dinámica, qué sucede cuando usas el “nuevo “Palabra clave en Java … ese tipo de cosas. Los cursos de CS tienden a saltear esas cosas.

Si tienes suerte, obtienes algo de exposición a C ++, donde tocarán brevemente nuevos y eliminarán, pero no profundizarán más que “¡recuerden eliminar sus vectores al final, amigos!”

No entendía los punteros en absoluto. El solo pensar en ellos me encerró el cerebro (aunque probablemente tenía 13 años o algo así … muchas cosas no tenían sentido). Finalmente, con la ayuda de mi padre (un tipo de sistemas integrados), finalmente los entendí. Son triviales! Es solo una dirección de su objeto, en lugar del objeto en sí.

Por supuesto, muchas personas todavía están confundidas acerca de por qué existen los punteros y por qué los necesitamos. Todavía no entienden por qué tenemos cosas como la pila y el montón, por qué uno se usa sobre el otro. Además, cuando se enseñan punteros, a menudo no se enseña en el contexto de un ejemplo del mundo real. La teoría es fácil, obviamente: un puntero contiene la dirección del valor al que apunta. Sencillo. ¿ Pero por qué es eso importante? La mayoría de las personas luchan con esa parte.

Para agravar el problema, prevalece la recolección de basura o ARC, en lenguajes populares como Swift, Java, Ruby, Python y C #, por nombrar algunos. Los punteros obviamente existen en estos lenguajes, pero no en el sentido del estilo C. Esto hace que sea muy fácil ignorar los punteros como concepto, si eres un principiante.

En general, los punteros se enseñan muy mal. En realidad no son un concepto difícil. La gestión de la memoria en su conjunto es un tema amplio y debe enseñarse mejor.

Dos cosas, principalmente:

  1. Que las variables y las matrices ocupan memoria
  2. Los punteros apuntan a la memoria, pero no la asignan por usted.

Como la mayoría de la gente ha notado, si sale del ensamblaje, tiene una idea más o menos intuitiva de ellos:

  • Un puntero es la dirección de una ubicación de memoria real que carga en un registro
  • Una desreferencia de puntero es cuando carga o almacena otro registro, indirecto, a través del primer registro, para acceder a los contenidos de la ubicación de memoria cuya dirección está en el primer registro

Sin embargo, si no tienes idea de cómo están organizadas las cosas en la memoria, los indicadores son realmente misteriosos.

Y casi todos los “lenguajes de enseñanza” simplificados, como Java o Python, o fingen que no tienen punteros, llamándolos “referencias” (haciéndote creer que son un alias que renombra algo), o en realidad no tienen punteros.

Esto se debe a que ya no enseñamos idiomas de computadora directamente.

Si fuiste a Harvard, te enseñan C en su introducción al curso de informática; no Java, y no Python. Las personas que se gradúan con un título de CS de Harvard, tienden a entender los punteros.

Las personas que se gradúen de alrededor de una docena de otras universidades en los Estados Unidos también comprenderán los indicadores, siempre que hayan tomado el énfasis en el grado correcto.

De lo contrario, probablemente no obtendrá “punteros”, hasta que alguien le dé un ejercicio, en el que tenga una matriz de bytes grande, y le haga administrar toda la memoria y las variables que usa en su programa desde esa matriz de bytes.

Esto no es algo que hacen en la mayoría de las universidades.

Usted posee una gestión de memoria de casi el 100%, y depende de usted usarla correctamente. Asigne memoria para cada variable de montón exactamente una vez y libérela exactamente una vez. Inicialícelo más de una vez y tendrá una pérdida de memoria; olvide inicializarlo o liberarlo dos veces y tendrá una vulnerabilidad de seguridad. Y el lenguaje le brinda aproximadamente cero herramientas para ayudar.

La otra cosa que confunde a las personas es el hecho de que los punteros son solo números, y puede manipularlos como tales (conocido como “aritmética de punteros”). Pero si arruina su aritmética, generalmente crea otra vulnerabilidad de seguridad, una que a menudo se manifiesta solo como un error simple (o ningún error) a menos que lo busque específicamente como un problema de seguridad. Esto también significa que si asigna un valor de datos directamente a un puntero (ptr = 123; en lugar de * ptr = 123;), está sobrescribiendo una dirección de memoria con un valor de datos, y nada en el lenguaje o compilador le impedirá haciendo eso Si un atacante puede controlar el valor de los datos, como si proviene de un documento, tiene otra vulnerabilidad de seguridad en sus manos.

¿Todas esas veces que su programa CS 101 causó una falla de segmentación? La mayoría de ellos eran probablemente vulnerabilidades de seguridad.

Puedo recordar bien que cuando comencé a aprender a programar, alrededor de los 22 años, la idea de una variable era difícil de entender por sí misma. No sé por qué, porque estaba muy familiarizado con lo que es básicamente la misma idea en matemáticas. Pero la programación no es álgebra; x = x + y es válido y útil en la programación, pero normalmente implicaría que y es cero en álgebra.

Cuando agrega la idea de una dirección de una variable, almacenada en otra variable, eso parece realmente confuso, a pesar de que nadie tiene un problema con la idea de una nota que le dice dónde encontrar otra nota. Para empezar, debe comprender que las variables se almacenan en algún lugar de la memoria de la computadora.

Además de eso, los asteriscos y los signos de unión utilizados en la notación de puntero en C se sienten muy confusos para un principiante. Tienen múltiples significados (p. Ej., Se usan para definir y desreferenciar un puntero), y C ++ agrega aún más significados “sobrecargados” para los símbolos de unión.

Puede tomar la dirección de un puntero y obtener un puntero a un puntero, que comienza a parecer demasiado complejo de manejar. Podemos entender fácilmente la oración “Lo sé”, podemos entender “Sé que lo sabes”, comenzamos a luchar un poco con “Sé que sabes que lo sé”, y agregar un nivel adicional de indirección se convierte en difícil de entender.

Lo que no entendí en ese momento, junto con muchos principiantes, es que aprender a usar punteros se trata principalmente de practicar su uso. Con práctica, los asteriscos y los signos de unión se colocan fácilmente en el lugar correcto, así como nuestros dedos encuentran fácilmente las posiciones correctas en los trastes de guitarra después de haber practicado los acordes de guitarra lo suficiente. Además, con la programación, no es necesario estirar los dedos ni endurecer la punta de los dedos. Muchos principiantes intentan aprender programación al desconcertarlo demasiado, mientras practican no lo suficiente. Eso hace que los punteros parezcan una verdadera mierda de cabeza.

[Por lo general, trato de evitar las blasfemias en Quora mientras lo uso felizmente en la vida real entre mis amigos, ya que Quora es una audiencia mixta de sensibilidades desconocidas, pero aquí “carajo” parece el término más apropiado …]

En mi experiencia, los punteros confunden a las personas que realmente no entienden la esencia de cómo una computadora ejecuta un programa.

Nunca he hablado con nadie familiarizado con la programación en lenguaje ensamblador que esté confundido por los punteros. En mi observación, siempre son las personas que solo han llegado a la programación a través de lenguajes de alto nivel (y toda su abstracción) quienes están confundidos.

Y para ser justos, tiene sentido. Si tengo una variable Z y le digo a la computadora que Z es 3, entonces ¿por qué tendría sentido que el puntero a Z sea 0x3F7B8692A4F923B7? Y la próxima vez que ejecute el mismo programa exacto, ¿el puntero a Z sería 0x2156AF83B21C8D01?

Esto tiene sentido si comprende que una variable es un valor almacenado en una ubicación en la memoria, y el puntero es la dirección en la memoria donde se almacena la variable pero no el valor almacenado en sí. Y si desea elevarlo un poco, que un controlador (un puntero a un puntero) puede contener una dirección donde se almacena una dirección.

Esto parece cegadoramente obvio para las personas que tienen una cierta comprensión básica de la forma en que una computadora funciona a nivel mecánico, pero como muchas cosas aparentemente cegadoramente obvias, si no tienes esa comprensión de fondo necesaria, es todo menos obvio.

Confunden el puntero con el valor al que intentan acceder.

Confunden el * y & con otra cosa, porque no conocen a los operadores C.

Están mezclados sobre pasar por referencia y pasar por valor y cuándo usar cada uno.

No entienden que C realiza la parte difícil de la aritmética del puntero para ellos.
los enteros son 4 bytes, pero los índices de matriz son enteros consecutivos que apuntan a direcciones a intervalos de 4 bytes, por lo que un [4], el quinto entero está en un desplazamiento de 20 bytes de un [0], no 4 o 5.

int * p = a + 3; / * p = 0xfffe0038 dirección de un +12 bytes * /
p ++; / * p = 0xfffe003C dirección de un +16 bytes * /
int x = * p / * x = copia del valor en la dirección 0xfffe003C * /

Muchas funciones de C se escriben para tomar un puntero como parámetro y también devolver un valor, a veces el mismo valor, a veces otra cosa. La escritura en C es lo suficientemente permisiva como para permitir que los valores de retorno no se asignen o se ignoren, lo que es perezoso y conveniente para hackear cosas, pero conduce a la confusión.

Los punteros no indican si están inicializados o no, o si apuntan a basura o no, de forma predeterminada. Por lo tanto, no sabe lo que tiene con un puntero a menos que se esfuerce para asegurarse de que el puntero se inicialice o se anule.

Un parámetro de paso por referencia se promocionará al tipo pasado incluso si no es realmente ese tipo de puntero, o incluso si el puntero no apunta a la memoria asignada o apunta a basura. Pero un objeto pasado por referencia no se copia, se copia el puntero. Si el puntero se altera en cuanto a su dirección dentro de la función, vuelve a su dirección original al completar la función. El objeto señalado por el puntero puede cambiar pero el puntero todavía apunta a la misma memoria. Esto puede ser sutil y confuso.

char * fun (char * s)
{
char * old = s;
s = “una cadena fija”;
volver viejo;
}

char * s = “mi cadena”;
char * t = diversión (s);

¿Qué es S?

que es t

t contendrá la misma dirección que s.

s no habrá cambiado.

El puntero fue copiado y reasignado a otra cadena

pero esa fue una copia temporal con alcance de función, y fue destruida cuando regresó la diversión.

Esto confunde a los nuevos programadores cuando “no hace lo que querían” y lo que ellos mismos se convencieron parecía correcto.

Los punteros no tienen idea de si la dirección a la que apuntan es válida. Esto es tan malo que C ++ inventó referencias para remediar este problema. El tiempo de ejecución de C supone que el puntero apunta a datos válidos del tipo correcto, pero no tiene forma de verificarlo.

La asignación de memoria dinámica devuelve la memoria como un puntero nulo, adecuado para que cualquier tipo de datos sea promovido a puntero a un tipo conocido. Debe asegurarse de asignar suficientes bytes para contener sus datos.

La liberación de punteros a cosas en C se puede realizar en punteros a datos no dinámicos y eso bloqueará los programas. Los elementos complejos pueden necesitar funciones de liberación complejas.

El alcance de un puntero es el mismo que el de cualquier variable. Por lo tanto, todos los errores de alcance de las variables también se aplican a los punteros.

Creando un puntero.

int * ptr; // caso 1
int * ptr; // caso 2
int * ptr; // caso 3

Todo lo anterior significa lo mismo.

int * ptr1, ptr2; // caso 1
int * ptr1, * ptr2; // caso 2

¡No son lo mismo!

En el caso 1, ptr2 no es un puntero.

Entonces, para facilitar la lectura, es mejor mantener el * más cerca del nombre de la variable.


Desreferenciar un puntero

Utiliza el operador * para crear un puntero. Utiliza el mismo * para desreferenciar un puntero. Bastante confuso, ¿verdad?

int * ptr = & x;
printf (“% d”, * ptr); // Desreferenciar el puntero

¿Qué pasa con el código a continuación?

int * ptr;
printf (“% d”, * ptr); // Deferencia el puntero

Está intentando desreferenciar un puntero no inicializado. ¿Lo que sucederá?

Según el alcance de ptr, tendrá algún valor. Tal vez algún valor de basura. Pero para un puntero, es una dirección de memoria.

En la siguiente línea, está intentando acceder a esa ubicación de memoria, que probablemente no podrá. ¿Resultado final? Fallo de segmentación.

¿Qué pasa si el código está debajo?

int * ptr = NULL;
printf (“% d”, * ptr); // Deferencia el puntero


Pasar un puntero a una función.

Lo que la mayoría de la gente no entiende es que C solo admite llamadas por valor . He leído varios libros de texto que van en contra. Toda basura.

int x = 10;
vacío foo (int * ptr) {
ptr = & x;
}

Cambia la ubicación de ptr en la función. ¿Pero se refleja fuera de la función? ¡No! ¿Por qué? Porque C solo admite llamadas por valor .

Lo que necesitas es

int x = 10;
vacío foo (int ** ptr) {
* ptr = & x;
}

Pasas un puntero a un puntero. ptr la dirección de memoria de x dentro de ptr .

Ahora se refleja fuera de la función.


Cómo leer una expresión de puntero.

char * const (* (* const foo) [5]) (int)

¿Qué significa lo anterior?

Comience con la variable.

foo

Ahora ve a la izquierda. * const , es decir, foo es un puntero const .

Ahora ve a la derecha. [5] , i, e, foo es un puntero const a una matriz de 5 elementos.

Ahora ve a la izquierda. * , es decir, foo es un puntero const a una matriz de 5 elementos, y cada uno de esos elementos de la matriz son punteros.

Ahora ve a la derecha. char * const ()(int) , es decir, foo es un puntero const a una matriz de 5 elementos, cada uno de los cuales son punteros de función a una función que toma un int y devuelve un puntero const a un char .

Los punteros de función no son fáciles.

Necesitas practicar mucho para darle sentido.

C galimatías ↔ Inglés puede ser útil.

El puntero en C le dice dónde está una variable, y para las personas que han llegado a la computación a través de lenguajes de nivel superior que manejan este concepto por usted, esta idea de que una variable puede tener una ubicación puede ser muy confusa.

En muchos idiomas, una variable es simplemente un par de nombre / valor. Puedes hacer referencia a esa variable por nombre, y eso es prácticamente todo lo que puedes hacer. Este enfoque de las variables es simple y poderoso, y es difícil de equivocarse, pero esto puede hacer que ciertas tareas sean imposibles o ineficientes, y es la razón por la cual algunos códigos pueden verse como “lentos”.

En C tiene acceso completo y completo a la máquina en sí misma, o al menos a la parte de la máquina visible para usted dentro de un programa, lo que le permite abordar dispositivos de hardware y manipular la memoria usted mismo. Algunas ubicaciones de memoria son más baratas de usar que otras, y usted tiene el poder en C para elegir la mejor ubicación para el trabajo.

Si no comprende cómo una CPU ejecuta un programa, si no comprende qué es la pila y cómo funciona, y si no comprende de dónde provienen los bloques de memoria cuando los solicita, un puntero en C va a tener muy poco sentido.

Si realmente quiere entender C, aprenda uno de los lenguajes ensambladores. Si bien es posible que no escriba mucho en lenguaje ensamblador, aprender lo que hace la máquina mientras ejecuta su programa eliminará el misterio de lo que hace su código en los lenguajes de nivel superior.

Como mucha gente dijo en otras respuestas, la forma en que se enseñó realmente me confundió. Para comprender cualquier idioma, debe tener una comprensión muy amplia de muchos aspectos de la informática para que muchos de los conceptos tengan sentido.

Desafortunadamente, no es factible enseñar tanto en una sola clase, y el orden en que enseñas las cosas no está completamente claro. Si aprende punteros sin haber aprendido acerca de la memoria y la arquitectura de la computadora, tendrá que conformarse con una abstracción para los punteros que luego serán reemplazados después de tomar una clase de arquitectura de computadora. Personalmente luché con estas abstracciones, y no fue hasta que terminé la arquitectura de la computadora y los compiladores (que estaban en mi último año de universidad) cuando finalmente todo hizo clic. Personalmente, podría haberme beneficiado de un enfoque más básico, donde primero se aprende lógica digital, luego arquitectura de computadora, que ensamblaje, luego C.

Otro problema con los punteros es que pueden ser simplemente confusos . Todavía no entiendo muy bien este algoritmo, principalmente debido a los punteros: el algoritmo del árbol de sufijos de Ukkonen en inglés simple.

Muchos principiantes piensan que lo que están pasando son variables, cuando lo que están pasando son valores.

int x = 0; // no, x no es 0; El valor de x es cero.
int y = x; // no, no estás haciendo que y se convierta en x; estás copiando el valor de x en y.
x = 1; // ahora cambias el valor de x, que no es y.

A menudo puede ver esto cuando las personas intentan pasar “una variable” a una función, y luego hacen cambios en “la variable”, pero después de devolver todos los cambios se borran, ¿qué es esto?

Creo que esta es la verdadera fuente principal de confusión, porque cuando aparecen los indicadores, los malos maestros les dicen a los principiantes “¡los indicadores son solo variables!”. ¡Pero eso es con lo que los principiantes piensan que están trabajando para empezar! Entonces, ¿cómo tiene sentido eso? Si enseñáramos a los niños primero a distinguir entre una variable y su valor, podríamos decir esas cosas sin confundirlas. Pero eso requeriría enseñar una semántica adecuada , y muchas personas parecen ser reacias a la idea de enseñar a los programadores qué significan realmente las expresiones y declaraciones que escriben (¿quizás porque es demasiado abstracto y demasiado parecido a las matemáticas?).

Entonces, por supuesto, puede anidar punteros, como en int ***x , y de repente necesita mantener toda una pila de información en su cabeza (es la dirección de una región de memoria que contiene la dirección de una región de memoria que contiene la dirección de una región de memoria que contiene un valor int – hey, he visto esto en alguna parte antes:

)

Otros malos maestros les dicen cosas como “¡no te preocupes, los punteros son solo números!” O tonterías similares (“¡cero es el puntero nulo!” Es otro). ¿Cómo puede la gente no confundirse cuando se les enseña mentiras como esa?

Finalmente para la sintaxis, muchos maestros no les recuerdan a sus alumnos que en C, la declaración sigue al uso. Entonces, cuando declaras algo que usas así *x , lo declaras como *x . En cambio, las personas intentan hacer coincidir & y * y otras cosas, ponen el * con el int, y así sucesivamente, y viven vidas muy infelices después de eso.

Muy simple. No es entender lo directo de las referencias o cuadros de ubicación de memoria, eso es simple. Más bien, es la indirección y la carga cognitiva que conlleva trabajar con la sintaxis del puntero en escenarios del mundo real. Puede tomar práctica aprender patrones comunes, pero aún puede ser confuso cuando se trata de un puntero a un puntero a una matriz en una estructura, etc.

La mayoría de las veces, el código que es difícil de entender es probablemente un antipatrón. O es la temida unión de punteros de estructura. ¡Sí, estoy hablando de ese reino de C que puede confundir a cualquiera si no tiene cuidado, enchufes BSD! Junto con la falta de documentación fácil de localizar (pocos o ningún comentario de documentación en línea en los encabezados …) y páginas de manual de espaguetis, los zócalos BSD son un ejemplo de dónde esto puede ser difícil.

Sin algún estudio de buenos ejemplos, uno puede descender rápidamente al código que compila porque el compilador tampoco puede decir si va a hacer algo mal.

Las funciones de Posix pueden caer en esto también. Hacen cosas que podrían sorprender a alguien entrenado con estilos de codificación más modernos. Los libros y las escuelas a menudo no enseñan parámetros o parámetros de entrada o que tendrías un parámetro de salida y un retorno del mismo valor sin error, pero usando un valor de error súper global … sí.

C nunca hizo favores a nadie en este departamento. La gente tiene que aprender buenos patrones, a menudo leyendo códigos.

Los buenos marcos pueden ayudar, porque tienden a centrarse en buenos patrones, pero sin ver un buen código de ejemplo, puede ser casi imposible adivinar cómo usarlo correctamente.

A2A.

Realmente deseo saberlo. Tuve un mes de programación de Assembler en mí antes de encontrar punteros, por lo que nunca tuve ningún problema con los punteros.

En Assembler no tiene la oportunidad de no saber que una variable necesita algo de espacio en la RAM reservada y que cualquier nombre que le dé a ese lugar en la RAM no es más que una forma más fácil de recordar la dirección en la memoria en la que está almacenada su variable.

Y un puntero no es más que otro espacio en la RAM que contiene la dirección de su variable real, y para alguien que trabaja en Assembler, realmente no hay nada grande en qué pensar.

Entonces, creo que las personas que están confundidas por los punteros probablemente no tienen un buen modelo en mente sobre cómo funciona la RAM; Creo que nunca llegaron a internalizar el concepto, que una variable no es solo un nombre para hacer referencia a un valor, sino que todas y cada una de las variables realmente tienen una dirección en la memoria. Si no lo sabe, la idea de que esa dirección es en realidad datos que puede manipular en su programa puede ser confusa.

Especialmente en C, la elección de ‘*’ para la sintaxis del puntero podría aumentar la confusión, ya que un principiante probablemente primero piense en “multiplicación” antes de “puntero”. Entonces, en retrospectiva, la ‘@’ podría haber sido una mejor opción, pero, por otro lado, el correo electrónico aún no se inventó, por lo que podría haber sido tan confuso y probablemente no tendríamos nuestra sintaxis de dirección de correo electrónico actual.

Y un último consejo (controversia :-)):

declarar punteros como:

int * a;

en lugar de

int * a;

De esta manera, puede pensar que ‘ *a ‘ ES un int, lo que podría ayudar.

También,

int * a, b;

parece que tanto a como b serían punteros a int, pero no lo son; a es un puntero a int y b es un int. Pienso que:

int * a, b;

Eso lo demuestra mucho más claro.

¡Que comience la guerra de llamas!

Creo que tengo una respuesta a tu pregunta, pero primero déjame contarte una historia. Es nuestra primera semana en el departamento de Ingeniería Informática, primer laboratorio de Estructura de Datos para el curso y para el semestre, puede expresarlo como su primera impresión (que puede durar para todo el departamento).

El laboratorio fue diseñado para depurar un pequeño programa (solo clase principal) escrito en C ++, con puntos de interrupción por todas partes, y para resolver el laboratorio, debe completar una hoja Respuestas a algunas preguntas relacionadas con el lugar donde se almacenan algunas variables , cambia el puntero, cambia el valor, lo que puede notar sobre una matriz, si está apuntando al primer elemento, luego crea un nuevo puntero que apunta al segundo elemento y así sucesivamente.

Recuerdo cómo algunas personas lloraron en este laboratorio y cómo algunos decidieron cambiar su departamento principal después de ese laboratorio.

Sí, es difícil aprender punteros, especialmente si esa es su primera pista, creo que sí, se le debe dar algún tiempo para comprender algo sobre cómo funcionan las computadoras, y luego podría ser presentado a los punteros.

Pero puede hacer que las personas entiendan rápidamente cómo funcionan los punteros.

El problema es que las personas les enseñan con abstracción excesiva.

Los punteros son bestias muy simples: son valores enteros que representan direcciones de memoria. Nada más y nada menos.

Una vez que le enseñas a la gente qué es RAM, y aprenden que las estructuras de datos, no importa cuán “abstractas” parezcan, son solo bits formateados de RAM, entonces los punteros tienen sentido. Si los golpeas en la cabeza con muchas clases, objetos y otros detritos, y luego esperas que “obtengan” punteros, pensarán que los punteros son otra abstracción elegante y se confundirán aún más.

A menudo, los programadores que provienen de más lenguajes de “alto nivel” necesitan un poco de desaprendizaje antes de “captar” por completo los punteros …

La mayor parte de mi confusión inicial fue sobre por qué uno necesitaría usar, por ejemplo, un puntero a la variable entera en lugar de la variable en sí. Esto desaparece cuando uno se da cuenta de la necesidad de acceso a la memoria asignada en tiempo de ejecución en lugar de tiempo de compilación.

En segundo lugar, creo que la gente es un poco vaga acerca de qué es una variable … basically a human readable alias for a location in memory that contains a value to be interpreted as data of the variable’s type. Once they understand that, it’s not too hard to understand that, for example a variable of type int*, such as int* p, that p is a human readable alias for a place in memory that contains a value that is another place in memory that contains a value with data to be interpreted as type int.

The concept isn’t too difficult a one to wrap your head around, but if you’re learning the language without much other experience programming (or even with some experience in another higher-level language), it is a thoroughly daunting concept.

That and the fact that mention of pointers is normally accompanied by “be careful using this stuff, or you may mess up badly” makes it not the most encouraging topic to tackle.

Finally, C’s syntax for expressing, dereferencing and assigning pointers was probably the single thing that made it most difficult for me to grasp, at least when trying to actually write code that used them or trying to understand code that uses them. The pointer syntax shares characters used in the bitwise-and and the multiplication operators. I’d have much rather that keywords or some other redundant symbol were used, but alas that decision has already been made.

More Interesting

¿Qué se necesita para convertirse en desarrollador de juegos?

¿Por qué China se centra en la fabricación de gama baja, mientras que India es dominante en la industria de software de gama alta?

Me estoy especializando en Ingeniería de Computadores, quiero ser un Arquitecto de Software o gerencia para desarrolladores de software, ¿cuál sería la mejor opción para seguir?

Como alguien sin un título en Ciencias de la Computación y sin experiencia profesional en programación, ¿debo anunciar que completé un bootcamp de desarrollador en mi currículum?

¿Cuáles son los mejores recursos (artículos, libros, ejemplos) para diseñar API? ¿Cuáles son algunas características de una hermosa API?

¿Hay alguna diferencia entre ser un DevOps en una gran empresa y un ingeniero de software en una startup?

¿Qué implica reequilibrar un anillo hash?

¿Pueden los ingenieros informáticos trabajar con el software y el hardware de una computadora?

En seguridad informática, ¿qué es un sandbox?

Al diseñar la arquitectura de datos para una nueva aplicación móvil, ¿cuáles son algunas de las mejores prácticas relacionadas con la escalabilidad?

¿Cuánta programación debes saber antes de aprender DS & Algo?

¿Se pagará a los desarrolladores web a la par de los desarrolladores de software?

¿El SEO está relacionado con el software?

¿Alguien ha trabajado alguna vez dentro del paradigma de programación de pares profesionalmente?

Quiero comenzar un proyecto de programación por diversión. ¿Cuáles son los pasos que debo seguir antes de escribir líneas de código?