Je devrais commencer par dire que C et C ++ ont été les premiers langages de programmation que j'ai appris. J'ai commencé avec le C, puis beaucoup fait du C ++ à l'école, puis je suis retourné au C pour le maîtriser.
La première chose qui m'a dérouté à propos des pointeurs lors de l'apprentissage de C était la simple:
char ch;
char str[100];
scanf("%c %s", &ch, str);
Cette confusion était principalement enracinée dans le fait d'avoir été introduit à l'utilisation de la référence à une variable pour les arguments OUT avant que les pointeurs ne me soient correctement présentés. Je me souviens que j'ai sauté les premiers exemples en C pour les nuls parce qu'ils étaient trop simples pour ne jamais faire fonctionner le premier programme que j'ai écrit (probablement à cause de cela).
Ce qui était déroutant à ce sujet, c'était ce &ch
que signifiait réellement et pourquoi str
n'en avait pas besoin.
Après m'être familiarisé avec cela, je me souviens avoir été confus au sujet de l'allocation dynamique. J'ai réalisé à un moment donné qu'avoir des pointeurs vers des données n'était pas extrêmement utile sans allocation dynamique d'un certain type, alors j'ai écrit quelque chose comme:
char * x = NULL;
if (y) {
char z[100];
x = z;
}
pour essayer d'allouer dynamiquement de l'espace. Ça n'a pas marché. Je n'étais pas sûr que cela fonctionnerait, mais je ne savais pas comment cela pourrait fonctionner autrement.
J'ai appris plus tard sur malloc
et new
, mais ils me semblaient vraiment des générateurs de mémoire magique. Je ne savais rien de leur fonctionnement.
Quelque temps plus tard, on m'enseignait à nouveau la récursivité (je l'avais appris par moi-même auparavant, mais j'étais en classe maintenant) et j'ai demandé comment cela fonctionnait sous le capot - où étaient stockées les variables séparées. Mon professeur a dit "sur la pile" et beaucoup de choses sont devenues claires pour moi. J'avais déjà entendu le terme et j'avais implémenté des piles de logiciels auparavant. J'avais entendu d'autres parler de "la pile" bien avant, mais je l'avais oublié.
À cette époque, j'ai également réalisé que l'utilisation de tableaux multidimensionnels en C pouvait être très déroutante. Je savais comment ils fonctionnaient, mais ils étaient tellement faciles à comprendre que j'ai décidé d'essayer de les utiliser chaque fois que je le pouvais. Je pense que le problème ici était principalement syntaxique (en particulier leur passage ou leur retour à partir de fonctions).
Depuis que j'écrivais du C ++ pour l'école pour la prochaine année ou deux, j'ai acquis beaucoup d'expérience en utilisant des pointeurs pour les structures de données. Ici, j'ai eu une nouvelle série de problèmes - mélanger les pointeurs. J'aurais plusieurs niveaux de pointeurs (des choses comme node ***ptr;
) me trébucher. Je déréférerais un pointeur le mauvais nombre de fois et j'aurais finalement recours à la détermination du nombre dont *
j'avais besoin par essais et erreurs.
À un moment donné, j'ai appris comment fonctionnait le tas d'un programme (en quelque sorte, mais assez bien pour qu'il ne me tenait plus éveillé la nuit). Je me souviens avoir lu que si vous regardez quelques octets avant le malloc
retour du pointeur sur un certain système, vous pouvez voir la quantité de données réellement allouée. J'ai réalisé que le code malloc
pourrait demander plus de mémoire du système d'exploitation et que cette mémoire ne faisait pas partie de mes fichiers exécutables. Avoir une idée de travail décente de la façon dont malloc
fonctionne est vraiment utile.
Peu de temps après, j'ai suivi un cours d'assemblage, qui ne m'a pas appris autant sur les pointeurs que la plupart des programmeurs le pensent probablement. Cela m'a amené à réfléchir davantage à l'assembly dans lequel mon code pourrait être traduit. J'avais toujours essayé d'écrire du code efficace, mais maintenant j'avais une meilleure idée de la façon de le faire.
J'ai également suivi quelques cours où j'ai dû écrire des lisp . Lors de l'écriture de lisp, je n'étais pas aussi préoccupé par l'efficacité que je l'étais en C.J'avais très peu d'idée en quoi ce code pourrait être traduit s'il était compilé, mais je savais qu'il semblait utiliser beaucoup de symboles nommés locaux (variables). les choses sont beaucoup plus faciles. À un moment donné, j'ai écrit du code de rotation d'arborescence AVL dans un peu de lisp, que j'ai eu du mal à écrire en C ++ à cause de problèmes de pointeur. J'ai réalisé que mon aversion pour ce que je pensais être des variables locales excessives avait entravé ma capacité à écrire cela et plusieurs autres programmes en C ++.
J'ai également suivi un cours de compilateurs. Pendant que dans ce cours, je suis passé au matériel avancé et j'ai appris à propos de l' assignation unique statique (SSA) et des variables mortes, ce qui n'est pas si important que cela, sauf que cela m'a appris que tout compilateur décent fera un travail décent en traitant des variables qui sont plus utilisé. Je savais déjà que plus de variables (y compris des pointeurs) avec des types corrects et de bons noms m'aideraient à garder les choses en tête, mais maintenant je savais aussi que les éviter pour des raisons d'efficacité était encore plus stupide que mes professeurs moins soucieux de la micro-optimisation ne le disaient. moi.
Donc pour moi, connaître un peu la disposition de la mémoire d'un programme a beaucoup aidé. Penser à ce que signifie mon code, à la fois symboliquement et sur le matériel, m'aide. L'utilisation de pointeurs locaux qui ont le bon type aide beaucoup. J'écris souvent du code qui ressemble à:
int foo(struct frog * f, int x, int y) {
struct leg * g = f->left_leg;
struct toe * t = g->big_toe;
process(t);
de sorte que si je bousille un type de pointeur, l'erreur du compilateur indique clairement quel est le problème. Si j'ai fait:
int foo(struct frog * f, int x, int y) {
process(f->left_leg->big_toe);
et avoir un type de pointeur erroné là-dedans, l'erreur du compilateur serait beaucoup plus difficile à comprendre. Je serais tenté de recourir à des changements par essais et erreurs dans ma frustration, et probablement aggraver les choses.