Debería comenzar diciendo que C y C ++ fueron los primeros lenguajes de programación que aprendí. Comencé con C, luego hice C ++ en la escuela, mucho, y luego volví a C para hablarlo con fluidez.
Lo primero que me confundió acerca de los punteros al aprender C fue lo simple:
char ch;
char str[100];
scanf("%c %s", &ch, str);
Esta confusión se debió principalmente a haber sido introducida al uso de referencias a una variable para argumentos OUT antes de que los punteros me fueran presentados correctamente. Recuerdo que omití escribir los primeros ejemplos en C para Dummies porque eran demasiado simples solo para nunca tener el primer programa que escribí para trabajar (muy probablemente debido a esto).
Lo que era confuso sobre esto era lo que &ch
realmente significaba y por qué str
no lo necesitaba.
Después de familiarizarme con eso, recuerdo haber estado confundido sobre la asignación dinámica. En algún momento me di cuenta de que tener punteros a los datos no era extremadamente útil sin una asignación dinámica de algún tipo, así que escribí algo como:
char * x = NULL;
if (y) {
char z[100];
x = z;
}
para tratar de asignar dinámicamente algo de espacio. No funcionó. No estaba seguro de que funcionaría, pero no sabía cómo podría funcionar.
Más tarde aprendí acerca de malloc
y new
, pero realmente me parecieron generadores de memoria mágica. No sabía nada sobre cómo podrían funcionar.
Algún tiempo después, otra vez me enseñaron la recursión (lo había aprendido por mi cuenta antes, pero ahora estaba en clase) y pregunté cómo funcionaba bajo el capó, dónde estaban almacenadas las variables separadas. Mi profesor dijo "en la pila" y muchas cosas me quedaron claras. Había escuchado el término antes y había implementado pilas de software antes. Había escuchado a otros referirse a "la pila" mucho antes, pero lo había olvidado.
Alrededor de este tiempo también me di cuenta de que usar matrices multidimensionales en C puede ser muy confuso. Sabía cómo funcionaban, pero era tan fácil enredarse en eso que decidí tratar de evitar usarlos siempre que pudiera. Creo que el problema aquí era principalmente sintáctico (especialmente al pasar o devolverlos de las funciones).
Como estaba escribiendo C ++ para la escuela durante el próximo año o dos, obtuve mucha experiencia en el uso de punteros para estructuras de datos. Aquí tuve un nuevo conjunto de problemas: mezclar punteros. Me gustaría que varios niveles de punteros (cosas como node ***ptr;
) me hicieran tropezar. Quitaría la referencia de un puntero la cantidad incorrecta de veces y eventualmente recurriría a calcular cuántas*
necesitaba por prueba y error.
En algún momento aprendí cómo funcionaba el montón de un programa (más o menos, pero lo suficientemente bueno como para que ya no me mantuviera despierto por la noche). Recuerdo haber leído que si mira unos pocos bytes antes de que malloc
regrese el puntero que en cierto sistema, puede ver cuántos datos se asignaron realmente. Me di cuenta de que el código en malloc
podría pedir más memoria del sistema operativo y esta memoria no era parte de mis archivos ejecutables. Tener una idea funcional decente de cómo malloc
funciona es realmente útil.
Poco después de esto, tomé una clase de asamblea, que no me enseñó tanto sobre punteros como la mayoría de los programadores probablemente piensan. Me hizo pensar más sobre en qué ensamblado podría traducirse mi código. Siempre había tratado de escribir código eficiente, pero ahora tenía una mejor idea de cómo hacerlo.
También tomé un par de clases donde tuve que escribir un poco de lisp . Al escribir lisp, no estaba tan preocupado por la eficiencia como en C. Tenía muy poca idea de a qué se podría traducir este código si se compilaba, pero sabía que parecía utilizar muchos símbolos (variables) locales con nombre. Las cosas son mucho más fáciles. En algún momento escribí un código de rotación de árbol AVL en un poco de ceceo, que me costó mucho escribir en C ++ debido a problemas con el puntero. Me di cuenta de que mi aversión a lo que pensaba que eran variables locales en exceso había impedido mi capacidad de escribir eso y varios otros programas en C ++.
También tomé una clase de compiladores. Mientras estaba en esta clase, pasé al material avanzado y aprendí sobre la asignación única estática (SSA) y las variables muertas, lo cual no es tan importante, excepto que me enseñó que cualquier compilador decente hará un trabajo decente al tratar con variables que son ya no se usa Ya sabía que más variables (incluidos los punteros) con los tipos correctos y los buenos nombres me ayudarían a mantener las cosas claras en mi cabeza, pero ahora también sabía que evitarlas por razones de eficiencia era aún más estúpido de lo que me dijeron mis profesores menos optimistas. yo.
Entonces, para mí, conocer un poco sobre el diseño de memoria de un programa me ayudó mucho. Pensar en lo que significa mi código, tanto simbólicamente como en el hardware, me ayuda. El uso de punteros locales que tienen el tipo correcto ayuda mucho. A menudo escribo código que se parece a:
int foo(struct frog * f, int x, int y) {
struct leg * g = f->left_leg;
struct toe * t = g->big_toe;
process(t);
así que si arruino un tipo de puntero, por el error del compilador queda muy claro cuál es el problema. Si lo hice:
int foo(struct frog * f, int x, int y) {
process(f->left_leg->big_toe);
y si hay algún tipo de puntero incorrecto allí, el error del compilador sería mucho más difícil de resolver. Me sentiría tentado a recurrir a cambios de prueba y error en mi frustración, y probablemente empeoraría las cosas.