Rédaction tutoriel C

(auto)recrutement

a marqué ce sujet comme résolu.

Oui, sauf que la comparaison ne tient pas la route : le concept de type n'existe pas en Assembleur (celui de fonction non plus d'ailleurs), du coup il ne lui est pas nécessaire de disposer d'une déclaration pour vérifier la concordance des types entre le prototype et l'appel.

Attends, je te la refais. Pour moi, un langage qui est capable de vérifier la concordance des types dans ce code source…

1
2
3
4
5
6
7
int mafonction (int param1, char param2) {
    return 42;
}

int main() {
    return mafonction(12, 'a');
}

…mais pas dans celui-ci…

1
2
3
4
5
6
7
int main() {
    return mafonction(12, 'a');
}

int mafonction (int param1, char param2) {
    return 42;
}

…j'appelle ça un langage teubé. Mais j'ai bien conscience que c'est une conception personnelle, hein. :D

c'est précisémment une des raisons qui me pousse à utiliser le C et à considérer l'utilisation de la plupart des autres langages comme une hérésie étant donné leur consommation monstrueuse de ressources comparé au C

Je comprends cette logique de la recherche de performances, mais j'avoue me poser la question de sa pertinence quand :

  • on a des machines au moins mille fois plus puissantes que celles pour lesquelles le C a été optimisé, et que certains langages de très haut niveau comme le Haskell arrivent la plupart du temps à générer un exécutable qui tourne à peine 2 à 3 fois plus lentement que son équivalent en C ;
  • cette performance s'obtient au prix d'un temps considérable passé sur la programmation, parce que pour citer Natalya (grande amatrice du C, je rappelle) « pour réinventer la roue, il faut redéfinir la géométrie ».

C'est surtout ce dernier point sur lequel nous achoppons. En codant en assembleur, on peut parfois obtenir des programmes plus rapides et dix fois plus petits que leurs homologues en C, mais rien que convertir un nombre en chaîne de caractères est un calvaire : le C produit un code moins optimisé, mais le gain en confort de programmation en vaut la chandelle. Ben c'est pareil pour le C vis-à-vis de certains langages de plus haut niveau (pas tous, hein, je parle pas de PHP).

En outre, les langages de haut niveau permettent généralement de réduire le temps de déboguage et d'augmenter la réutilisation du code existant. En programmation fonctionnelle ou logique, par exemple, si une fonction pure fonctionne au moment de sa programmation, elle fonctionnera toujours quel que soit le contexte dans lequel elle sera réutilisée, parce qu'elle ne permet pas les effets de bord. L'absence d'accès trop direct à la mémoire garantit aussi de ne pas se manger une erreur de segmentation un jour ou l'autre. Etc.

En résumé, la perte de temps à l'exécution est avérée mais apparaît négligeable au regard du gain de temps à la programmation et à la correction, ainsi qu'au regard des erreurs évitées à l'exécution.

Par exemple, est-ce que tu accepterais qu'un mathématicien ne sache pas compter et n'aie aucune maitrise des formules trigonométriques ou analytiques de base juste parce qu'on a des outils modernes comme des calculettes ou des logiciels de calcul formels ?

Ne pas savoir compter risquerait, en effet, d'être rédhibitoire. Cela étant, si un apprenti mathématicien est capable de penser directement en termes de groupes, corps et anneaux, que le fait de ne pas voir de différence fondamentale entre additionner des entiers, additionner des vecteurs et combiner des symétries lui simplifie la vie et lui permet d'accéder plus vite aux résultats que nous mettons actuellement une bonne décennie de maths à atteindre, je ne vois aucune raison de lui enseigner autrement.

Après tout, c'est comme ça qu'on fait dans l'apprentissage de la grammaire, surtout d'une langue étrangère : on présente la structure générale et on donne des exemples de mise en pratique. Pourquoi ne pas présenter la structure générale (les groupes) et ensuite des exemples particuliers (les entiers, les vecteurs) ? Mis à part que cela nous paraît inhabituel ? Cette bataille à couteaux tirés nous montre qu'il n'est pas forcément judicieux d'introduire le concept « de base » de nombre dans l'enseignement des maths, vu qu'en progressant on finit par se rendre compte qu'il est excessivement difficile à définir.

Et c'est un point qui m'a fait tiquer plusieurs fois dans ton intervention : à aucun moment on ne définit une méthodologie pour décider de ce qui est « de base » et de ce qui ne l'est pas, on se contente de considérer que ce que l'on enseigne actuellement en début de formation constitue les bases. Mais ce n'est pas nécessairement le cas. On peut tout à fait sauter directement à un niveau d'abstraction différent et enseigner à partir de là sans se préoccuper de ce qu'il y a en dessous, du moins jusqu'à ce qu'ayant acquis un niveau avancé on touche aux limites que ce niveau d'abstraction tient du niveau inférieur.

Pour faire une analogie, je n'ai pas besoin de comprendre quoi que ce soit à la chimie sous-jacente pour apprendre que les pâtes sauce au poivre sont plus réussie si je fais cuire les pâtes dans la sauce qui si j'ajoute la sauce aux pâtes préalablement cuites : je suis à un niveau d'abstraction supérieur, et il vaut mieux apprendre les bases de ce niveau d'abstraction directement plutôt que passer plusieurs années à étudier la chimie pour préparer du risotto.

Et je doute sincèrement que « les primitives de base des langages procéduraux » soient un pré-requis indispensable à l'apprentissage de la programmation. Se représenter SHA-1 comme un compacteur qui prend des entrées de taille fixe pour les fusionner les unes dans les autres et obtenir une sortie de la même taille quel que soit le nombre d'entrées est à mon avis plus efficace et plus simple que se le représenter comme un ensemble de cases mémoires auxquelles on fait subir un certain nombre de déplacements et opérations. La programmation fonctionnelle est tout à fait abordable par un parfait débutant, à condition de réserver pour plus tard la compréhension fine du fonctionnement réel de la machine deux niveaux d'abstraction plus bas.

Si quelqu'un veut commencer par le C comme premier langage pour se préparer à d'autres langages, il en a parfaitement le droit, et écrire un tutoriel pour ce genre de personnes est une bonne chose

Si quelqu'un qui connaît le Python veut découvrir le Ruby pour savoir ce qu'il a de différent avec le Python, il en a parfaitement le droit, mais ce n'est pas pour autant que le tutoriel « Ruby pour les Pythonistes » est un choix pertinent pour un cours unique de Ruby sur le ZdS. Parce que la politique éditoriale ici est de ne pas multiplier les tutos qui abordent un thème très similaire, et en particulier, de n'avoir qu'un seul big-tuto from scratch par langage. Dans ces conditions, il faut écrire un cours de C dont l'approche sera pertinente pour le plus de visiteurs possibles et non parce qu'une catégorie donnée d'iceux « a le droit » d'avoir le tuto de ses rêves. Et en l'occurrence, on est nombreux à penser que l'approche pertinente est de considérer le C comme un langage du type « découvrez de plus près comment fonctionne votre machine » au même titre que l'assembleur.

+2 -0

Pour faire une analogie, je n'ai pas besoin de comprendre quoi que ce soit à la chimie sous-jacente pour apprendre que les pâtes sauce au poivre sont plus réussie si je fais cuire les pâtes dans la sauce qui si j'ajoute la sauce aux pâtes préalablement cuites : je suis à un niveau d'abstraction supérieur, et il vaut mieux apprendre les bases de ce niveau d'abstraction directement plutôt que passer plusieurs années à étudier la chimie pour préparer du risotto.

Dominus Carnufex

Sauf que dans le cas de la programmation, tu ne peux pas implémenter d'algorithmes (même SHA-1) sans utiliser les structures de bases : essaye de coder sans appels de fonctions et sans boucles en Java ou en Python (quoiqu'en utilisant les constructions fonctionnelles…) pour t'en rendre compte.

Et en l'occurrence, on est nombreux à penser que l'approche pertinente est de considérer le C comme un langage du type « découvrez de plus près comment fonctionne votre machine » au même titre que l'assembleur.

Dominus Carnufex

Je vais être très clair : le C ne t'apprend que très peu de choses qui soient bas-niveau, et je considère que l'utiliser pour mieux comprendre sa machine est inutile. C'est ce que j'ai essayé de faire dans le tutoriel sur PDP, et à part l'alignement mémoire dans le chapitre sur les structures, il n'y a rien qui ne soit abordable dans tout autre langage. Ce que j'ai abordé dans ce tutoriel sur les pointeurs, les tableaux et sur les variables est obligatoirement vu dans tout bon cours portant sur n'importe quel autre langage impératifs et objet.

Seul l'assembleur et les langages de description matériel sont utiles pour mieux comprendre sa machine. Et encore : je pense clairement qu'un bon cours d'architecture des ordinateur (comme celui que j'ai écrit) est nettement plus utile et efficace de ce point de vue.

+1 -0

Dans la vraie vie, quand on veut parler une langue étrangère, l'important est de savoir ce qu'on souhaite dire et de prendre le dictionnaire et en général la personne arrive à peu près à s'en sortir pour expliquer ce qu'il veut !

Dans l'informatique c'est pareil, si tu sais faire (algorithmie), le langage de programmation n'est là que pour communiquer avec ton PC… Et quand on sait ce dont on a besoin, on prend la documentation et on s'y atèle avec le langage adapté.

La base c'est l'algorithmie, connaître les entrailles de sa machine n'a strictement aucun intérêt quand on fait de la programmation pour avoir un résultat !

Si maintenant c'est pour la culture, eh bien pourquoi pas apprendre ce que vous voulez, les goûts et les couleurs, ça ne se discute pas.

Personnellement j'ai commencé par python, les ISN commencent par ce langage très généralement, voir java de temps en temps, et quand ça ne me suffisait pas (besoin de performance) j'ai appris le C et utiliser ces connaissances pour interfacer les deux langages. Apprendre python comme 1er langage ne m'a absolument pas pénalisé pour apprendre le C…

Et puis je me suis mis à coder un peu pour Android, et là c'est le drame, je connaissais à peine java, et pourtant je le trouvais plus intuitif que python pour faire cela… Que cela ne tienne, j'ai appris java sur le tas, et on s'en sort très bien !

Bref, peu importe le langage qu'on choisira d'apprendre, si tu sais pas faire, ça ne sera pas à cause de lui (langage) que tu n'arriveras pas au bout de ton projet. ;)

essaye de coder sans appels de fonctions et sans boucles en Java ou en Python (quoiqu'en utilisant les constructions fonctionnelles…) pour t'en rendre compte

C'est bien, parce que ta réponse se saborde sans que j'aie à y toucher.

Les structures de base de la programmation procédurale sont, en effet, indispensables à l'apprentissage de… la programmation procédurale, merci Capitaine. En revanche, elles sont parfaitement inutiles pour apprendre la programmation fonctionnelle (et même parfois nocives, des concepts comme « fonction » ou « variable » n'ayant pas du tout le même sens dans les deux paradigmes). En conséquence, apprendre directement la programmation fonctionnelle sans passer par l'apprentissage du procédural plus proche du fonctionnement réel de la machine, ce n'est pas griller les étapes mais se simplifier la vie1. ;)

Je vais être très clair : le C ne t'apprend que très peu de choses qui soient bas-niveau, et je considère que l'utiliser pour mieux comprendre sa machine est inutile. C'est ce que j'ai essayé de faire dans le tutoriel sur PDP, et à part l'alignement mémoire dans le chapitre sur les structures, il n'y a rien qui ne soit abordable dans tout autre langage. Ce que j'ai abordé dans ce tutoriel sur les pointeurs, les tableaux et sur les variables est obligatoirement vu dans tout bon cours portant sur n'importe quel autre langage impératifs et objet.

  • Les pointeurs sont inexistants dans presque tous les langages de haut niveau, c'est le compilo/interpréteur qui se charge de passer des valeurs ou des pointeurs/références. Donc si, il n'y a guère que dans un tuto de C(++) qu'il est pertinent d'aborder cet aspect d'optimisation, surtout très tôt dans l'apprentissage.
  • Les tableaux des langages de haut niveau ne sont qu'une des nombreuses sortes de « collections de variables » à côté des tables de hachage, des structures et des tuples, et il n'y a guère que le C(++) pour lequel cela présente une importance que ces variables soient organisée d'une certaine manière en mémoire.
  • Un certains nombre de langages de haut niveau (comme le Python) implémentent nativement l'arithmétique multi-précision et la question de la taille maximale d'une variable numérique n'a juste pas de sens : un tuto de C non seulement est obligé d'aborder cet aspect, mais est aussi le plus adapté pour expliquer le fonctionnement de GMP, la bibliothèque d'arithmétique multi-précision utilisée en sous-main par presque tous les autres langages.
  • Les langages de haut niveau se contrefoutent que la génération d'un exécutable à partir de plusieurs fichiers sources passe par l'étape « fichier objet » et ne te demandent pas de programmer en prenant cela en compte, contrairement aux headers du C(++).
  • La perméabilité en terme de typage des variables du C est directement lié à leur représentation en mémoire : bon nombre de langages de haut niveau vont gueuler si tu fais un xor entre deux caractères ; le C s'en fout, parce qu'un char, c'est un entier sur un seul octet.
  • Etc.

Je vais être très clair : le C t'en apprend carrément plus sur le fonctionnement de ta machine et de ton système que les langages de haut niveau.


  1. Attention, je ne dis pas que la prog fonctionnelle est mieux, je dis que si l'on veut apprendre la prog fonctionnelle, il vaut mieux ne pas connaître la prog procédurale. 

+0 -0
  • Les pointeurs sont inexistants dans presque tous les langages de haut niveau

Dominus Carnufex

Tout les langages de haut niveau camouflent ce genre de choses sous le terme de référence ou sous des objets similaires. Et si on veut aborder proprement ces objets de manière compréhensibles, on doit parler au minimum d'adresses mémoires, et donc de pointeurs (vécu dans mes cours de Java donnés en Fac, c'était très drôle…).

  • Les tableaux des langages de haut niveau ne sont qu'une des nombreuses sortes de « collections de variables » à côté des tables de hachage, des structures et des tuples, et il n'y a guère que le C(++) pour lequel cela présente une importance que ces variables soient organisée d'une certaine manière en mémoire.

Dominus Carnufex

Tous les tableaux sont organisés comme ça en mémoire : c'est obligé, et sans cela, les complexités algorithmiques changent totalement. Par exemple, si on remplaçait les RAM par des mémoires associatives, on n'aurait pas besoin de tables de hachage : la recherche dans un tableau serait en O(1). Et il existe certaines mémoires où les complexités des listes changent et sont plus ou moins similaires à celles qu'on trouve avec des tables de hachage. Et on pourrait poursuivre avec toutes les structures de données : les complexités algorithmiques ne valent que si la mémoire RAM reste ce qu'elle est.

  • La perméabilité en terme de typage des variables du C est directement lié à leur représentation en mémoire : bon nombre de langages de haut niveau vont gueuler si tu fais un xor entre deux caractères ; le C s'en fout, parce qu'un char, c'est un entier sur un seul octet.

Dominus Carnufex

C'est une problématique de Typage faible, pas de hardware , ce qui n'a rien à voir. Par exemple, certaines architectures refuseraient de XOR deux caractères directement au niveau de leur langage machine, tandis que d'autres implémentent un typage dynamique en hardware, avec gestion du polymorphisme version OO.


Sinon, je pense que programmation fonctionnelle et procédurales sont trop différentes pour qu'on puisse observer des phénomènes d'interférences. C'est bien beau de dire qu'apprendre du procédural avant le fonctionnel rend celle-ci plus difficile à acquérir, mais je suis persuadé que c'est faux : la programmation fonctionnelle est réellement difficile, pas intuitive, voire même incompatible avec nos mécanismes de raisonnement usuels (ah, et c'est pareil pour l'OO, pour les mêmes raisons).

+0 -1

Bon, je viens de tout relire, et en fait ma vision des choses était déjà en gros ce vers quoi la majorité se dirigeait. Cf. entre autres les propositions de GuilOooo :

Un nouveau point qui semble se dégager des derniers posts, c'est que le C sert à faire des choses bas niveau. On a parlé de programmation système, je suis sûr que dans 3-4 posts quelqu'un va parler de programmation de micro-contrôleur et quelqu'un d'autre de créer un OS. C'est clairement la bonne direction (tout en restant très généraliste).

À mon humble avis, les TPs finaux (i.e. le big-projet qui prend une partie complète du tuto à lui tout seul) du cours de C pourraient être :

  • recoder un malloc/free simple (avec un lien vers un tuto avancé pour les gens que ça motive) ;
  • coder un système de fichiers minimaliste (qui stockerait lui-même les données dans un unique fichier, pourquoi pas) avec les opérations open/close/write/read ;
  • coder un (dé)compresseur Zip, ou PNG, ou autre format analogue (du point de vue des algorithmes) ;
  • recoder un shell comme sh, bash, ksh, version minimaliste ;
  • recoder un émulateur pour une VM/un ByteCode/une machine simple, par exemple un embrayon d'émulateur NES (ou autre console old-school) ;
  • créer un OS x86 ultra-minimaliste (on peut s'épargner le bootloader en utilisant qemu pour tester, il permet grosso modo de lancer un exécutable on the bare metal en appelant main() directement) ;
  • faire un truc avec un microcontrôleur (plus délicat).

L'idée serait d'écrire tout le tuto en vue de réaliser l'un (ou plusieurs, ou tous) les TPs proposés ci-dessus. Ça orienterait directement les lecteurs vers ce pour quoi le C est fait, plutôt que de leur faire miroiter la création d'un jeu vidéo (qui, bien que possible, est une grande galère pour un débutant en C).

GuilOooo

Je propose juste d’aller encore plus loin avec quelque chose de plus large qu’un simple cours de C. Les propositions de GuilOooo reviennent en gros à enseigner le C en arrivant progressivement à ses applications (la programmation système ici). Moi, je proposais le parcours inverse. En fin de compte, on retrouvera le même contenu, sauf que le langage C n’apparaît pas comme une fin mais comme un moyen, et que ça nous autoriserait du contenu supplémentaire en-dehors du langage C (genre se familiariser avec de l’assembleur, je trouverais ça chouette).

Mais en fait, j’avoue ne pas savoir à quoi ressemble un bête cours de système, c’est peut-être simplement de ça dont je rêve…

Une autre raison que j’ai oubliée d’expliciter dans mon message précédent et qui pour moi justifie un cours de C sous cette forme, c’est le fait que le C soit encore incontournable, et donc que c’est utile/nécessaire de l’enseigner, mais dans le but de ne pas être démuni si on s’y trouve confronté, et non de programmer avec par défaut. Ce qui est bien expliqué par Dominus Carnufex :

Alors est-ce que je le recommanderais à un débutant total ? Plus maintenant. Maintenant, je lui dirais d'aller voir du côté du Haskell. Seulement, pour des raisons historiques, dont j'ai parlé plus haut, il est difficile (et un peu suicidaire je pense) d'être totalement ignorant du C quand on commence à avoir un niveau avancé. D'où l'utilité de savoir lire du C sans pour autant en maîtriser toutes les subtilités, il y a assez de catalogues de référence pour cela. Parce que…

Dominus Carnufex


[…] Comme l'a remarqué Maelan, je suis d'avis que le C est un langage généraliste et qu'en conséquence il peut être utilisé pour n'importe quel projet. […]

Taurre

Hé, je disais le contraire !

[…] Mais utiliser du C dans n'importe quel projet par pur dogmatisme, c'est aussi idiot que d'écrire un système d'exploitation en Ruby.

Dominus Carnufex

Le nœud de nos divergences se situent ici, je pense. Comme l'a remarqué Contrairement à Maelan, je suis d'avis que le C est un langage généraliste et qu'en conséquence il peut être utilisé pour n'importe quel projet. Qu'est ce qui justifie un tel point de vue apparemment anachronique et dogmatique ? Pas grand chose si ce n'est une recherche personnelle de contrôle et de performance. Comme tu l'as dit : « il n'existe aucun langage (hors assembleur) qui permette de produire des programmes aussi rapides, peu gourmands en mémoire et de petite taille que ceux bien programmés en C » et c'est précisémment une des raisons qui me pousse à utiliser le C et à considérer l'utilisation de la plupart des autres langages comme une hérésie étant donné leur consommation monstrueuse de ressources comparé au C.

Taurre

Je comprend parfaitement ce point de vue. J’ai longtemps eu le même avis : malgré tous les gens qui répétaient qu’avec les ordinateurs actuels, ce n’est pas bien grave d’utiliser un ou deux octets de plus que nécessaire pour une structure de donnée, je trouvais ça simplement stupide de gaspiller les ressources ainsi sans aucune raison. Cet entêtement m’a empêché un moment d’accepter de programmer à un niveau plus élevé, mais la pénibilité de coder en C a finalement eu raison de moi : j’en ai eu marre de devoir systématiquement recoder les listes chaînées et simuler la généricité de façon très inconfortable et approximative avec void*, alors que plein de langages offrent nativement ces fonctionnalités.

D’ailleurs Taurre, tous les langages de plus haut niveau que le C n’induisent pas un surcoût inacceptable. Par exemple, OCaml est bâti sur le C (comme plein d’autres), et la surcouche est assez légère (sauf peut-être en ce qui concerne les appels de fonctions, je ne sais pas comment c’est implémenté mais le caractêre fonctionnel, la curryfication, les définitions locales, etc. doivent compliquer les choses). Les données simples et structurées sont codées de la même façon, le seul surcoût concerne les types de base (entiers, booléens…) pour lesquels un bit est utilisé par le langage. Ça me paraît un prix tolérable en contrepartie des fonctionnalités que ce modèle mémoire permet : polymorphisme gratuit et ramasse-miettes. Les performances sont donc comparables à celles du C, et on y gagne tant en expressivité (polymorphisme) qu’en sécurité (on ne peut pas faire n’importe quoi avec nos données, par exemple il n’y a pas d’équivalent des unions du C, à la place on a directement les types sommes qui sont une combinaison d’une union et d’un label, conformément à l’usage usuel des unions). Je parle d’OCaml car c’est mon nouveau langage de prédilection, mais je suis certain qu’il y en a plein d’autres.

+0 -0

Le nœud de nos divergences se situent ici, je pense. Comme l'a remarqué Maelan, je suis d'avis que le C est un langage généraliste et qu'en conséquence il peut être utilisé pour n'importe quel projet.

Construire sa maison en partant du feu et du silex est une option envisageable : il faut simplement recréer tous les outils dont tu vas avoir besoin. C'est techniquement viable, juste ni productif ni efficace, surtout quand tu peux avoir accès à des outils modernes. Quand c'est la seule option envisageable, bah on la saisis, car ca reste mieux que de devoir redécouvrir le feu.

Davidbrcz

Sauf que construire une maison et apprendre à construire une maison n'a strictement rien à voir. Il existe d'ailleurs des courants pédagogiques, en accord avec les recherches sur l'expertise, qui mettent en garde contre cette erreur assez courante : pour former des experts, on ne doit pas faire réfléchir les élèves comme des experts de manière trop précoce(et donc leur faire utiliser des outils d'experts).

Pour revenir à ton analogie, tu peux (et tu dois) certes utiliser directement tes outils modernes pour construire une maison, mais le faire lors de l'apprentissage donnerait certainement des choses assez invraisemblables. Par exemple, est-ce que tu accepterais qu'un mathématicien ne sache pas compter et n'aie aucune maitrise des formules trigonométriques ou analytiques de base juste parce qu'on a des outils modernes comme des calculettes ou des logiciels de calcul formels ?

Et ce genre de chose a lieu en informatique : regardez un petit peu les messages précédents postés dans ce sujet, et regardez le nombre de ceux qui vantent un abord trop précoce de l'algorithmique, de la programmation orientée objet, de la programmation fonctionnelle (totalement incompatible avec la méthode de raisonnement par simulation mentale utilisée par les apprentis programmeurs : Voir quelques études récentes de Johnson-Lair), ou de la fabrication de code robuste (si si) avant même que les bases essentielles soient acquises. Au passage, les bases en question sont la notional machine (au moins, le C force à l'aborder, contrairement aux autres langages), puis les primitives de base des langages procéduraux . Mine de rien, ces bases posent de gros problèmes aux débutants, et il est facile de brûler les étapes.

C'est un erreur d'invoquer des arguments du type : pas besoin de faire un tutoriel pour débutant sur le C, vu que celui-ci n'est utilisable nulle part en milieu professionnel ou dans un projet. Au contraire : plus un langage est utile pour le programmeur expert, plus il a de chances d'être mauvais pour le programmeur débutant. Un langage pour l'apprentissage n'a rien à voir avec un langage utilisable dans un projet. Autant dire que bon nombre d'arguments contre le C comme premier langage ne sont pas recevables.

Mewtow

J’avoue ne pas bien saisir ton propos. Si je comprend bien, tu dis qu’il faut commencer bas niveau parce que c’est plus simple en termes de notions à ingérer, ou alors parce que comprendre le bas niveau est nécessaire pour les niveaux supérieurs d’abstraction.

Si c’est bien ce que tu sous-entends, ça me semble faux. Je n’ai pas de sources aussi sérieuses que les tiennes à t’opposer, mais par exemple, enseigner ZFC et la théorie des ensembles à des collégiens ne me parait ni nécessaire pour leur parler de trigonométrie ou de dénombrement, ni bien avisé : l’influence bourbakiste dans l’enseignement des mathématiques à une époque s’est montrée désastreuse.

Ton exemple avec les calculatrices ne me semble pas valable : on ne parle pas d’outils pour se faciliter les tâches pénibles tout en restant conscient de leur existence. On parle d’abstraction, c’est-à-dire de masquer complètement ces aspects derrière des notions de plus haut niveau. Et ça, ça me semble au contraire faciliter l’apprentissage puisque ça diminue le nombre de concepts à avaler.

Pour revenir à l’informatique, quand j’implémente un traitement sur un arbre syntaxique défini par un type somme, je me fous complètement de savoir que les étiquettes des nœuds sont en fait codées par des entiers, eux-mêmes des visions de l’esprit pour des paquets de n transistors qu’on voit comme des chiffres binaires. Je n’ai besoin que d’une seule notion : le type somme.

édit : Si j’ai tout compris de travers, je serais ravi que tu me corriges, références à l’appui.

+1 -0

Je vais être très clair : le C ne t'apprend que très peu de choses qui soient bas-niveau, et je considère que l'utiliser pour mieux comprendre sa machine est inutile. C'est ce que j'ai essayé de faire dans le tutoriel sur PDP, et à part l'alignement mémoire dans le chapitre sur les structures, il n'y a rien qui ne soit abordable dans tout autre langage. Ce que j'ai abordé dans ce tutoriel sur les pointeurs, les tableaux et sur les variables est obligatoirement vu dans tout bon cours portant sur n'importe quel autre langage impératifs et objet.

  • Les pointeurs sont inexistants dans presque tous les langages de haut niveau, c'est le compilo/interpréteur qui se charge de passer des valeurs ou des pointeurs/références. Donc si, il n'y a guère que dans un tuto de C(++) qu'il est pertinent d'aborder cet aspect d'optimisation, surtout très tôt dans l'apprentissage.

Dominus Carnufex

Selon la taxinomie que l'on trouve sur wikipédia et qui place tous les langages de 3e génération au même niveau, donc je peux parler d'Ada et de Pascal. Et bien, ils ont des pointeurs. Ils sont bien moins importants qu'en C, mais ils on ont. Les références que l'on voit partout ailleurs, sont aussi des pointeurs. Encapsulés, mais cela en sont.

En C++, un bon cours retarde les pointeurs jusqu'au dernier moment. En gros avant le polymorphisme d'inclusion, il n'y a pas nécessité d'en parler. A contrario du C qui les exige dès l'écriture de swap(), ou pire pour gérer des collections.

  • Les tableaux des langages de haut niveau ne sont qu'une des nombreuses sortes de « collections de variables » à côté des tables de hachage, des structures et des tuples, et il n'y a guère que le C(++) pour lequel cela présente une importance que ces variables soient organisée d'une certaine manière en mémoire.

Dominus Carnufex

Dans tous les langages, le cache sur la RAM est un facteur important qui impacte ce que nos utilisateurs observent.

Bref. Je ne suis pas persuadé non plus que le C permette vraiment de comprendre la machine. Il y a quantité de choses qui restent masquées à chaque étape et que l'on ne voit même pas quand on code en C.

(Sinon, +1 à C est inadapté pour un cours from scratch.)

(et je suis content que la discussion soit ressortie des avantages et désavantages du C – indépendamment d'un contexte d'apprentissage – car ce n'est pas le sujet.)

J’avoue ne pas bien saisir ton propos. Si je comprend bien, tu dis qu’il faut commencer bas niveau parce que c’est plus simple en termes de notions à ingérer, ou alors parce que comprendre le bas niveau est nécessaire pour les niveaux supérieurs d’abstraction.

Si c’est bien ce que tu sous-entends, ça me semble faux. Je n’ai pas de sources aussi sérieuses que les tiennes à t’opposer, mais par exemple, enseigner ZFC et la théorie des ensembles à des collégiens ne me parait ni nécessaire pour leur parler de trigonométrie ou de dénombrement, ni bien avisé : l’influence bourbakiste dans l’enseignement des mathématiques à une époque s’est montrée désastreuse.

Ton exemple avec les calculatrices ne me semble pas valable : on ne parle pas d’outils pour se faciliter les tâches pénibles tout en restant conscient de leur existence. On parle d’abstraction, c’est-à-dire de masquer complètement ces aspects derrière des notions de plus haut niveau. Et ça, ça me semble au contraire faciliter l’apprentissage puisque ça diminue le nombre de concepts à avaler.

Pour revenir à l’informatique, quand j’implémente un traitement sur un arbre syntaxique défini par un type somme, je me fous complètement de savoir que les étiquettes des nœuds sont en fait codées par des entiers, eux-mêmes des visions de l’esprit pour des paquets de n transistors qu’on voit comme des chiffres binaires. Je n’ai besoin que d’une seule notion : le type somme.

édit : Si j’ai tout compris de travers, je serais ravi que tu me corriges, références à l’appui.

Maëlan

On est d'accord que commencer par la partie la plus élémentaire n'est pas toujours nécessaire, ni même souhaitable : j'aborde d'ailleurs le sujet dans mon tutoriel sur la pédagogie en contrastant méthode "synthétique" et méthode "analytique". Mais dans le cas de la programmation, on a des expériences qui montrent que c'est le cas, qu'on peut parfaitement expliquer avec les théories actuelles de la conception de code source.

Un grand nombre de ces recherches ont clairement montré que donner à un élève un modèle bas-niveau de la machine avant et pendant l'apprentissage de la programmation permet d'améliorer ses performances en algorithmique et en programmation comparé à des élèves qui ne reçoivent pas cet enseignement. Et les études sur les programmeurs novices permettent de comprendre pourquoi.

Créer un code source consiste à planifier une succession d'opérateurs, chaque opérateur étant une expression ou un opérateur compatible avec le langage de programmation utilisé. Ce processus s’effectue en trois étapes, y compris chez ceux qui n'ont jamais appris à programmer :

Lors de l'étape de compréhension, le programmeur va construire une représentation de la situation initiale, composée d'un graphe de concepts reliés entre eux par des relations logiques, presque isomorphe à la situation réelle : on ça appelle un modèle mental, ou modèle de situation.

Dans les tâches de programmation, on trouve deux modèles mentaux :

  • un qui représente la situation à modéliser informatiquement : le modèle du domaine ;
  • et le second qui correspond à l'état de la machine, c'est à dire à l'ensemble des valeurs ou variables ainsi que le control flow du programme (l'état de la machine pour les langages procéduraux) : le modèle du programme.

Ces deux modèles sont analogues : à chaque entité du modèle du domaine correspond une structure dans le modèle du programme, et réciproquement. Le raisonnement des programmeur est donc essentiellement analogique. Certains modèles de compréhension de programme ajoutent parfois un troisième modèle mental, mais je passe cela sous silence (une revue de la littérature est disponible ici, pour les curieux : Software Comprehension – A Review & Research Direction).

Après l'étape de compréhension, notre cerveau va passer à l'étape de simulation, où notre cerveau va simuler mentalement l'évolution du modèle mental étape par étape. Chaque étape simulée sur le modèle du domaine est traduite en opérateur du modèle du programme par analogie entre ces deux modèles. On remarquera que cette simulation est séquentielle : on pense en impératif et en procédural, pas en objet ni en fonctionnel.

Si jamais aucun opérateur n'est compatible avec l'étape du modèle du domaine, le sujet peut décomposer l'étape en sous-étapes : cette stratégie s'appelle la means-end analysis. Les programmeurs experts raisonnent de la même manière, à une différence près : là où les novices décomposent des problèmes en sous-problèmes en top-down, les experts vont concevoir leur code en bottom-up (et c'est valable dans de nombreux domaines, et pas seulement pour la programmation).

Par la suite, lors de la phase d'analyse, le sujet analyse la suite d’opérateurs, pour en extraire certains motifs, comme des boucles, des conditionnelles, des fonctions, ou des structures de code de haut niveau, des structures de design, etc. Et à ce petit jeu, les programmeurs experts ont mémorisé, à force d'expérience, de nombreux motifs qu'ils peuvent réutiliser à loisir dans la phase d'analyse. A ce sujet, je recommande la lecture de l'article suivant : Expert programming : a schema based approach.

Dans tous les cas, concevoir un code source demande une certaine capacité de simuler mentalement l'évolution du modèle du programme. Et cette capacité à concevoir un code source peut être améliorée avec une instruction suffisamment bien conçue : il existe diverses recommandations pédagogiques, validées dans diverses expérimentations, qui se basent toutes sur le concept de notional machine.

Cette notional machine est une représentation de la machine définie par les constructions et primitives d'un langage de programmation. Le modèle informatique n'est d'ailleurs qu'une instance bien précise de la notional machine, où certaines zones mémoire se voient attribuer une valeur bien précise.

Divers chercheurs, le premier étant Du Boulay, contrastent ainsi l'approche black box, qui cache cette notional machine (le hardware susjacent), avec l'approche open-box qui explicite celle-ci au mieux. Et la seconde est clairement meilleure pour ce qui est de l'apprentissage de la génération et de la compréhension de code source : introduire de manière précoce cette machine, avant tout apprentissage de la programmation permet à l'élève de construire des modèles informatiques beaucoup plus fiables et complets.

De très nombreuses expériences ont ainsi montré qu'une introduction précoce de cette notional machine, ainsi qu'un enseignement qui fait explicitement référence à celle-ci, permet d’améliorer les "capacités algorithmiques" des élèves, leur capacité à programmer, à utiliser des boucles, etc. De mémoire, je me souviens notamment d'une étude faite par Mayer, en 1985

De nombreuses études ont aussi montré un effet positif des programmes de visualisation, qui illustrent ce qui se passent sous le capot, qui permettent de visualiser l'effet de chaque ligne de code sur l'état de la mémoire/de la notional machine. Une thèse intéressante à lire sur le sujet est disponible ici : Visualisation in introducory programming.

Évidemment, la notional machine n'est pas une machine réelle, avec ses spécificités d'implémentation, mais d'un modèle simplifié, qui contient juste quoi interpréter le fonctionnement des constructions du langage. Quoiqu'il en soit, plus une notional machine est explicite dans un langage (et c'est clairement le cas du C), meilleur sera l'apprentissage.

Par contre, la complexité de la notional machine est ce qui détermine la facilité de la simulation : plus elle est complexe, plus sa simulation demandera des ressources cognitives, attentionnelles, et mnésiques. Il existe généralement une notional machine idiomatique par paradigme, avec de légères variantes suivant les langages. Pour les langages procéduraux, la notional machine contient une RAM, des registres, un processeur, des instructions et notamment des branchements, un program counter, etc. Celle des langages objets-procéduraux contient en plus un tas pour l'allocation dynamique, des branchements indirects pour le late binding, etc : elle est donc plus complexe, ce qui rend l'apprentissage légèrement plus difficile. Pour les langages fonctionnels, il existe deux machines possibles : soit des machines à réécriture, soit des machines à réduction de graphe; qui sont toutes deux très complexes.

+1 -0

Attends, je te la refais. Pour moi, un langage qui est capable de vérifier la concordance des types dans ce code source… […] …mais pas dans celui-ci… […] …j'appelle ça un langage teubé. Mais j'ai bien conscience que c'est une conception personnelle, hein. :D

Dominus Carnufex

Ok, là, nous sommes d'accord, c'est effectivement un peu nul.

[…] Comme l'a remarqué Maelan, je suis d'avis que le C est un langage généraliste et qu'en conséquence il peut être utilisé pour n'importe quel projet. […]

Taurre

Hé, je disais le contraire !

Maëlan

Ma phrase veut juste dire que tu as remarqué que je considère le C comme un langage généraliste, mais pas que tu es du même avis que moi. ;)

Je comprend parfaitement ce point de vue. J’ai longtemps eu le même avis : malgré tous les gens qui répétaient qu’avec les ordinateurs actuels, ce n’est pas bien grave d’utiliser un ou deux octets de plus que nécessaire pour une structure de donnée, je trouvais ça simplement stupide de gaspiller les ressources ainsi sans aucune raison. Cet entêtement m’a empêché un moment d’accepter de programmer à un niveau plus élevé, mais la pénibilité de coder en C a finalement eu raison de moi : j’en ai eu marre de devoir systématiquement recoder les listes chaînées et simuler la généricité de façon très inconfortable et approximative avec void*, alors que plein de langages offrent nativement ces fonctionnalités.

Maëlan

Et je comprends parfaitement ton point de vue également. C'est clair que pas mal de choses sont rébarbatives en C ou nécessitent une bibliothèque tierce alors que cela semble basique. Maintenant, je n'ai pas encore atteint un stade où je considère cela comme pénible ou insupportable, cela viendra peut-être avec le temps (ou pas)…

D’ailleurs Taurre, tous les langages de plus haut niveau que le C n’induisent pas un surcoût inacceptable. Par exemple, OCaml est bâti sur le C (comme plein d’autres), et la surcouche est assez légère (sauf peut-être en ce qui concerne les appels de fonctions, je ne sais pas comment c’est implémenté mais le caractêre fonctionnel, la curryfication, les définitions locales, etc. doivent compliquer les choses). Les données simples et structurées sont codées de la même façon, le seul surcoût concerne les types de base (entiers, booléens…) pour lesquels un bit est utilisé par le langage. Ça me paraît un prix tolérable en contrepartie des fonctionnalités que ce modèle mémoire permet : polymorphisme gratuit et ramasse-miettes. Les performances sont donc comparables à celles du C, et on y gagne tant en expressivité (polymorphisme) qu’en sécurité (on ne peut pas faire n’importe quoi avec nos données, par exemple il n’y a pas d’équivalent des unions du C, à la place on a directement les types sommes qui sont une combinaison d’une union et d’un label, conformément à l’usage usuel des unions). Je parle d’OCaml car c’est mon nouveau langage de prédilection, mais je suis certain qu’il y en a plein d’autres.

Maëlan

En résumé, la perte de temps à l'exécution est avérée mais apparaît négligeable au regard du gain de temps à la programmation et à la correction, ainsi qu'au regard des erreurs évitées à l'exécution.

Dominus Carnufex

Finalement, on en revient un peu à une échelle de valeur où la question est de savoir s'il on est prêt à sacrifier en performance et en contrôle pour gagner du temps de développement. Pour ma part, je ne peux pas m'y résoudre pour l'instant (enfin, si, mais pas plus qu'au niveau du C).

+0 -0

Messieurs, je pense qu'on s'écarte un peu du sujet là, il serait donc bon que l'on recentre le sujet (voire renommer celui-ci et en ouvrir un nouveau tout propre pour le tuto) et que l'on s'intéresse à la rédaction d'un tutoriel C (voire (à voir) à la migration d'une ressource de qualité).

+4 -0

Je ne veux pas rentré dans le débat en profondeur. Je ne traiterais qu'un point.

Je pense clairement que le C est généraliste. Mais je pense que le différent vient de la définition de généraliste.

Si un langage qui permet de faire beaucoup de chose est un langage généraliste. En ce sens, le C est bien le langage le plus généraliste de tous(*). Si un langage généraliste est un langage qui devrait être utilisé dans de nombreux domaine, savoir si le C est généraliste est plutôt objectif. Si un langage généraliste est langage courrament utilisé dans de nombreux domaine alors non le C n'est pas un langage très généraliste.

Pour compléter mes dires GPL wikipédia.

(*) Je ne me sens pas limité en C contrairement à ce qu'on pourait croire. Quand je vois que mes codes d'il y a des années compilent sur mon smartphone(ARM v7), que j'utilise le même langage pour programmer mon robot et mon site web …

Le C n'est pas parfait. Mais on ne peut lui retirer ses nombreux domaines d'application …

+0 -0

Je ne vais pas rentrer dans le débat moi aussi parce que je n'ai pas assez de recul et d'expérience pour juger un tel sujet (et pas envie aussi). Je vais simplement parler de mon vécu.

J'ai commencé la programmation fin 2010 par le C. C'était la grande époque où sur le SdZ, le tutoriel C++ demandait des connaissances en C, ce qui est totalement débile, on est bien d'accord. J'ai fais du C et uniquement du C pendant presque deux ans. Pendant cette période, j'ai eu la chance d'avoir un "mentor", SofEvans, qui m'a beaucoup aidé à comprendre de nombreux concepts qui étaient compliqués (les structures par exemple) parce que je ne voyais pas leur intérêt ni comment les utiliser. Je pense d'ailleurs que c'est grâce à lui que je n'ai pas abandonné la programmation.

De ce petit récit se dégagent deux choses : le C n'a pas toujours été facile à appréhender (les structures notamment), mais le C m'a appris, à grand coup de "je comprends rien bouhouhou" à rechercher par moi-même les informations sur Internet, sur les forums, dans la documentation. J'ai acquis une grande autonomie et ça m'est très utile désormais. Et j'apprécie beaucoup plus les mécanismes d'autres langages, comme les templates en C++ par exemple.

Après, quand à dire si apprendre le C en premier est pertinent ou non, je ne me positionne pas. Comme je ne saurais jamais ce que ça aurait donné sur moi en commençant par un autre langage.

@ache, tu parles plutot de portabilite ici, plus precisemment au niveau du code source. Personne n'a remis en cause que le C pouvait etre utilise pour faire a peu pres n'importe quoi mais la question etait de savoir s'il etait adapte pour n'importe quoi.

Vraisembablement si c'etait le cas il y a 30 ans, ce qui pouvait justifier un apprentissage en premier langage et meme une sorte de rite initiatique, ce n'est plus le cas maintenant, ce qui m'amene a penser que creer une nouvelle resource avec cette approche n'est pas pertinente (et je rejoins donc l'avis de Maelan sur une resource qui s'applique a apprendre le langage dans un contexte dans lequel il devrait etre utilise).

A contrario, il existe de tres bonnes ressource technique sur le langage, pour tous niveau et importer ces resources est de toute maniere une bonne idee.

Generalemenent ceux qui aiment le C aiment reinventer la roue donc il me parait difficile de les convaincre qu'ecrire une millionieme tutoriel, obselete avant meme son ecriture, n'est pas une perte de temps pour tout le monde.

+3 -0

@ache, tu parles plutot de portabilite ici, plus precisemment au niveau du code source. Personne n'a remis en cause que le C pouvait etre utilise pour faire a peu pres n'importe quoi mais la question etait de savoir s'il etait adapte pour n'importe quoi.

Vraisembablement si c'etait le cas il y a 30 ans, ce qui pouvait justifier un apprentissage en premier langage et meme une sorte de rite initiatique, ce n'est plus le cas maintenant, ce qui m'amene a penser que creer une nouvelle resource avec cette approche n'est pas pertinente (et je rejoins donc l'avis de Maelan sur une resource qui s'applique a apprendre le langage dans un contexte dans lequel il devrait etre utilise).

A contrario, il existe de tres bonnes ressource technique sur le langage, pour tous niveau et importer ces resources est de toute maniere une bonne idee.

Generalemenent ceux qui aiment le C aiment reinventer la roue donc il me parait difficile de les convaincre qu'ecrire une millionieme tutoriel, obselete avant meme son ecriture, n'est pas une perte de temps pour tout le monde.

Höd

Je suis de cet avis aussi. S'il existe une ressource externe (en français ou en langues étrangères) de qualité, complète, à jour et pédagogique et si la licence le permet (liberté de modification/adaptation pour les ressources en anglais par exemple), importer ladite ressource me semble le meilleur choix possible (demander l'avis de l'auteur par gentillesse est un plus). On a un compte "Auteur externe" adapté pour cela (reste à préciser la source dans l'introduction) et ajouter un lien canonique lors de l'envoi en validation au besoin. Donc, si vous souhaitez partir sur cette idée, c'est possible.

Et ça n'entrerait pas en conflit avec l'idée de Stranger (cela serait d'ailleurs plus intéressant à rédiger pour les membres intéressés ici).

+0 -0

Bon, ben, le débat est un peu tué dans l'œuf

Dominus Carnufex

Je tiens à préciser plusieurs choses :

  • le cours qui viens d'être publié en beta date déjà d'il y a un certains temps, celui-ci ayant été originellement publié sur le SdZ (pour une fois que l'on ne réinvente pas la roue, on se fait engueuler, c'est du propre…) ;
  • le cours n'est pas terminé et peu parfaitement être modifié, potentiellement de manière importante, en vue de rejoindre les souhaits d'autres personnes ;
  • comme l'a dit Kje, nous n'empêchons personnes de créer un autre cours, nous ne faisons que proposer le nôtre.
+1 -0

Bonjour à tous,

Je me permets de remonter ce sujet afin d'obtenir quelques avis. Comme vous l'avez sûrement remarqué, Mewtow, Lucas-84, paraze, informaticienzero et moi-même avons entrepris de migrer le cours sur le C que nous avions rédigé à l'origine sur Pdp vers ZdS. Celui-ci a été grandement modifié et est désormais prêt pour une première validation. Enfin, presque…

EDIT: j'ai oublié de préciser que la troisième partie du cours ne fera sans doute pas l'objet de la première validation, cette dernière devant être encore remaniée.

La gestion d'erreur

Afin de boucler la seconde partie, il manque un chapitre relatif à la gestion d'erreur. Dans mon esprit, celui-ci devrait couvrir les sujets suivants :

  • la gestion de ressources ;
  • les assertions ;
  • l'affichage de messages d'erreurs à l'aide des fonctions perror() et strerror() ;
  • les dépassements de capacités.

Toutefois, comme vous le savez, la gestion de ressources est un problème épineux en C qui peut se résoudre de diverses manières : SESE, SEME, goto, exit(), setjmp() et longjmp(), etc. Or, j'avoue ne pas trop savoir à quel saint me vouer ni comment aborder le sujet. Plus précisémment, conseille-t-on aux débutants d'appeler exit() en cas d'échec d'une allocation de mémoire ou bien leur recommande-t-on de propager ce type d'erreurs ? Également, parle-t-on de toute les manières d'organiser un code source pour gérer les ressources ou bien se concentre-t-on sur une ou deux solutions (si oui, lesquelles) ? Enfin, que fait-on si une erreur se produit lors de la gestion d'erreur (par exemple un appel à fclose() qui foire après un appel à fputs() qui avait lui-même échoué) ?

Les unions

La deuxième partie du cours aborde la notion d'aggrégats. En toute logique, les unions auraient leur place dans cette partie. Toutefois, j'éprouve des difficultés pour trouver des exemples parlant et suffisamment simple. Parler des unions à ce stade vous paraît-il pertinent, si oui comment présenteriez-vous le sujet ?

Merci d'avance pour vos retours.

+2 -0

La gestion d'erreur est le point critique en C qui fait que le C n'est pas un langage de debutant si tu veux faire quelque chose de propre.

Message de lmghs tiré de Pdp (et y'en d'autres dans le thread).

En C, si tu veux un code qui squale, et qui gère correctement ses ressources, tu es vite amené à regrouper les libérations de ressources en fin de fonction. On force ainsi un unique point de sortie, le second SE de SESE. Oh, on peut aussi avoir des if imbriqués dans tous les sens avec des libérations au milieu, et des redondances. Ca, ce n'est pas sale, c'est dégueulasse. A la moindre modification, on a des fuites en perspectives.

Après, il y a setjump & cie. Mais vu la complexité d'emploi de la bête, rares sont les codes C qui s'en servent AMA. Cela demande un niveau d'expertise que n'a pas le développeur moyen à qui on dit "tiens, demain tu vas me maintenir ce code C que l'on a gagné". S'il y a des autres patterns, propres/idiomatiques qui ne se résument pas à centraliser à la fin de chaque fonction (SESE)/retarder la fin de fonction jusqu'à un point final qui demande la libération d'un ensemble de ressources (SESE encore), je suis preneur.

SESE n'est peut-être pas la plus utilisée (ce qui est un tord en C ; en C++ l'obliger est ridicule/hérétique/absdurde) dans la majorité des programmes, mais j'assimile ça à des mauvaises pratiques. Je ne connais pas de pattern propre en C (hors setjump) qui permette de garantir la libération de toute ressource allouée qui au final ne revienne pas à mettre en oeuvre du SESE (i.e. un seul point de sortie de la fonction).

Pour des exemples, les codes C "bons" de R.Chen ou de A.Lahman suivent tous le SESE pour gérer les ressources.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte