Site WWW de Laurent Bloch
Slogan du site

ISSN 2271-3905
Cliquez ici si vous voulez visiter mon autre site, orienté vers des sujets moins techniques.

Pour recevoir (au plus une fois par semaine) les nouveautés de ce site, indiquez ici votre adresse électronique :

Que penser du langage C ?
Une conversation avec Robert Ehrlich
Article mis en ligne le 7 novembre 2017
dernière modification le 9 novembre 2017

par Laurent Bloch
logo imprimer
Licence : CC by-nd

Après la mise en ligne de ma communication sur la conversion à Unix, une conversation animée s’est engagée avec Robert Ehrlich à propos du langage C, dont il est, bien plus que moi, un connaisseur dans les moindres détails. En voici la transcription. Les propos de Robert sont précédés du signal RE, les miens de LB.

 Inélégances de C

LB

Certains traits du langage C me sont restés inexplicables jusqu’à ce que je suive un cours d’assembleur VAX, descendant de leur ancêtre commun, l’assembleur PDP. J’ai compris alors d’où venaient ces modes d’adressage biscornus et ces syntaxes à coucher dehors, justifiées certes par la capacité exiguë des mémoires disponibles à l’époque, mais de nature à décourager l’apprenti. L’obscurité peut être un moyen de défense de techniciens soucieux de se mettre à l’abri des critiques.

Sans trop vouloir entrer dans la sempiternelle querelle des langages de programmation, je retiendrai deux défauts du langage C, dus clairement à un souci d’efficacité mal compris : l’emploi du signe = pour signifier l’opération d’affectation, et l’usage du caractère NUL pour marquer la fin d’une chaîne de caractères.

À l’époque où nous étions tous débutants, la distinction entre l’égalité [13] et l’affectation fut une affaire importante. En venant de Fortran, les idées sur la question étaient pour le moins confuses [1], et il en résultait des erreurs cocasses ; après avoir fait de l’assistance aux utilisateurs pour leurs programmes Fortran je parle d’expérience. Avec Algol, Pascal, LSE ou Ada, qui notaient l’égalité = et l’affectation := (sans parler de Scheme qui écrit set! ), c’était l’arrivée d’une distinction syntaxique claire, qui permettait de remettre les choses en place, c’était une avancée intellectuelle dans la voie d’une vraie réflexion sur les programmes.

Les auteurs de C en ont jugé autrement : ils notent l’affectation = et l’égalité ==, avec comme argument le fait qu’en C on écrit plus souvent des affectations que des égalités, et que cela économise des frappes au clavier. Ça c’est de l’ingénierie de haut niveau ! Dans un article du bulletin 1024 de la SIF [2], une jeune doctorante explique comment, lors d’une présentation de l’informatique à des collégiens à l’occasion de la fête de la Science, une collégienne lui a fait observer que l’expression i = i+1 qu’elle remarquait dans le texte d’un programme était fausse. Cette collégienne avait raison, et l’explication forcément controuvée qu’elle aura reçue l’aura peut-être écartée de l’informatique, parce qu’elle aura eu l’impression d’une escroquerie intellectuelle. Sa question montrait qu’elle écoutait et comprenait ce que lui disaient les professeurs, et là des idées durement acquises étaient balayées sans raison valable.

Pour la critique de l’usage du caractère NUL pour marquer la fin d’une chaîne de caractères je peux m’appuyer sur un renfort solide, l’article de Poul-Henning Kamp The Most Expensive One-byte Mistake [3]. Rappelons que ce choix malencontreux (au lieu de représenter une chaîne comme un doublet {longueur, adresse}) contribue aux erreurs de débordement de zone mémoire, encore aujourd’hui la faille favorite des pirates informatiques. Poul-Henning Kamp énumère dans son article les coûts induits par ce choix : coût des piratages réussis, coût des mesures de sécurité pour s’en prémunir, coût de développement de compilateurs, coût des pertes de performance imputables aux mesures de sécurité supplémentaires…

 Exemple de code source : scheduler du noyau Linux v0.01

Le scheduler est l’élément principal du système d’exploitation, il distribue le temps de processeur aux différents processus en concurrence pour pouvoir s’exécuter.

Les codes de ces systèmes sont aujourd’hui facilement disponibles en ligne, ici [4] et là [5] par exemple. Par souci d’équité (d’œcuménisme ?) entre les obédiences on n’aura garde d’omettre BSD [6], ici la version historique 4BSD.

  1. void schedule(void)
  2. {
  3. int i,next,c;
  4. struct task_struct ** p;
  5. /* check alarm, wake up any interruptible tasks that have got a signal */
  6. for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
  7. if (*p) {
  8. if ((*p)->alarm && (*p)->alarm < jiffies) {
  9. (*p)->signal |= (1<<(SIGALRM-1));
  10. (*p)->alarm = 0;
  11. }
  12. if ((*p)->signal && (*p)->state==TASK_INTERRUPTIBLE)
  13. (*p)->state=TASK_RUNNING;
  14. }
  15. /* this is the scheduler proper: */
  16. while (1) {
  17. c = -1;
  18. next = 0;
  19. i = NR_TASKS;
  20. p = &task[NR_TASKS];
  21. while (--i) {
  22. if (!*--p)
  23. continue;
  24. if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
  25. c = (*p)->counter, next = i;
  26. }
  27. if (c) break;
  28. for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
  29. if (*p)
  30. (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
  31. }
  32. switch_to(next);
  33. }

Télécharger

Avec l’aide de Patrick Cegielski [7] (p. 196) on observe que la variable c représente la priorité dynamique de la tâche [8] considérée. Le scheduler parcourt la table des tâches, et parmi celles qui sont dans l’état TASK_RUNNING, c’est-à-dire disponibles pour s’exécuter, il sélectionne celle qui a la priorité dynamique la plus élevée et lui donne la main : switch_to(next); (switch_to est un code assembleur introduit par un #define que l’on pourra lire dans le fichier include/linux/sched.h à la fin de l’article).

Bien que pour un texte C ce snippet (fragment de code) soit relativement propre, il illustre la raison de mes réticences à l’égard de ce langage. Et lorsque Peter Salus [9] (p. 77) écrit que C est un langage différent des autres langages de programmation, plus proche d’un langage humain comme l’anglais, on peut se demander s’il a un jour écrit une ligne de programme, en C ou autre chose.

 Discussion avec Robert Ehrlich

RE

Peut-on avoir plus de précision sur les raisons de cette réticence pour ce snippet de C ?

Je note au passage qu’il y a en préambule la mention v0.01, ce qui tendrait à signifier qu’il s’agit d’une toute première version. Ça me semble en contradiction avec la présence de void qui n’existait pas dans les premières versions de C. De même les sélecteurs de champs alarm, signal, counter dans la structure task_struct n’on pas de préfixe « discriminant » pour les distinguer d’autres sélecteurs de champ homonymes dans d’autres structures, ce qui était nécessaire dans les premières versions de C et ce qui se retrouve dans nombre de structure définies dans les .h sous /usr/include, bien que la nécessité ait disparu depuis.

LB

Il s’agit là de la version princeps de Linux, soit 1991, C était déjà dans sa maturité.

Mes réticences ? Je préfère les langages expressifs aux notations cryptiques. Je sais que ce n’est pas ton cas, mais j’ai assez connu d’adolescents prolongés heureux de faire des choses que personne ne pouvait comprendre. Il suffit de consulter les sites de publication de failles de sécurité pour constater les dégâts qui en résultent.

RE

Je ne vois toujours pas en quoi ce snippet illustre ces réticences, en quoi est-il plus cryptique qu’en un quelconque autre langage ?

LB

Comme je l’ai écrit, je dois concéder que ce code C est relativement propre. Restent le symbole d’affectation, et la ligne 25, dont la syntaxe ne m’a été expliquée par aucun des auteurs suivants : MM. Kernighan, Ritchie, Harbison, Steele Jr, Braquelaire et Lazard.

Pourrais-tu m’expliquer la seconde ligne ci-dessous :

  1. if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
  2. c = (*p)->counter, next = i;

Télécharger

RE

Là ce n’est pas le langage qui est cryptique, mais l’auteur du programme. Si j’avais eu à écrire ce bout de code, j’aurais écrit :

  1. if ((*p)->state == TASK_RUNNING && (*p)->counter > c) {
  2. c = (*p)->counter;
  3. next = i;
  4. }

Télécharger

La règle étant que ce qui est conditionné par le if est l’unique instruction qui suit, si on veut en mettre plusieurs, les encadrer par {} transforme ce groupe d’instructions en une instruction unique.

L’auteur de ces lignes, plutôt que d’utiliser cette méthode standard, a préféré utiliser une autre construction du langage peu usitée et dont la nécessité ne s’impose pas ici.

La syntaxe de C définit deux notions : instruction (statement dans la langue des créateurs) et expression (pareil dans les deux langues). Une expression est comme on s’y attend une combinaison (récursive) d’objets élémentaires par un certain nombre d’opérateurs (parmi lesquels l’affectation =). Une instruction a de nombreuse variantes (boucles for, do, while, conditionnelle if, ...) mais entre autres une expression suivie d’un point-virgule est une instruction.

Parmi les opérateurs pouvant intervenir dans une expression, il en est un peu connu dont le symbole est la virgule. C’est un opérateur binaire, il calcule aussi bien ce qui est devant la virgule que ce qui est derrière et la valeur de l’expression est ce second résultat. C’est cet opérateur que l’auteur du snippet a utilisé pour faire de deux expressions une seule, qui suivie d’un point-virgule devient une instruction, qui est donc l’unique instruction conditionnée par le if précédent.

J’avoue que c’est particulièrement inélégant dans ce cas. Le principe de C comme de nombreux autres langages est que le calcul d’une expression, d’une part produit une valeur, d’autre par peut produire certains effets que d’aucuns qualifient « de bord ». Qualification que j’aurais tendance à rejeter dans la mesure ou elle tend à dire que ce n’est pas l’essentiel, alors que tout particulièrement ici le but recherché des deux expressions composantes est l’effet de l’opérateur = qui est de modifier c et next. L’opérateur , est là pour dire qu’on ne s’intéresse qu’à l’effet de la première expression, mais à la valeur de la deuxième. Faire suivre ensuite l’expression complète d’un ; dit ensuite que finalement, non, on ne s’intéresse pas à cette valeur, comme quoi un caractère plus loin on peut être d’un avis contraire.

On peut se poser la question de l’utilité de cet opérateur , et des cas où son emploi est justifié. La raison de son existence tient pour l’essentiel à celle de l’expression conditionnelle <expr1>?<expr2>:<expr3> dont la valeur est <expr2> si <expr1> est vraie, <expr3> sinon. Exactement comme pour le if, on peut avoir plusieurs expressions à calculer en place de <expr2> ou <expr3>, seule la dernière fournissant la valeur de l’expression conditionnelle, l’opérateur , est là pour ça. Je dois reconnaître qu’une inélégance du langage C est cette distinction entre expression et instruction, qui oblige à deux constructions différentes (if(...)... else ... et (...?...:...) et deux mécanismes différents ( { et } d’une part, , d’autre part) pour résoudre la contrainte de l’instruction ou expression unique. Inélégance qu’évitent habilement LISP (et ses dérivés) et Algol68. En particulier en Algol68 if ... then ... else ... fi et ( ... | ... | ... ) sont strictement équivalents.

Quel que soit le langage, il y a moyen d’écrire du code inélégant, voire cryptique. Ce n’est pas le langage qui est à incriminer dans ce cas.

Une inélégance du même type fort répandue en C est d’écrire une instruction dont le seul but est d’incrémenter la variable x comme x++; plutôt que ++x;. La définition de l’opérateur ++ postfixé est d’incrémenter ce qu’il postfixe en ayant pour résultat la valeur avant incrémentation. Ecrire ++x; revient à dire « incrémente x et oublie » alors que x++ revient à dire « incrémente x mais souviens-toi de ce qu’il valait avant, non, finalement oublie ».

Une autre inélégance à mon goût personnel, mais je sais que tout le monde ne sera pas d’accord, présente dans ce même « snippet » est le while(1) auquel je préfère for(;;). Pour moi la première forme consiste à dire « boucle tant que le vrai n’est pas faux » alors que la deuxième dit simplement « boucle », autrement dit la première forme est une forme pédante de la deuxième.

Une dernière encore : pourquoi traîner partout ce (*p) plutôt que de déclarer un struct task_struct *q; et d’y mettre *p aux bons moments. Lesquels sont ligne 7 en remplaçant if (*p) par if(q = *p) et ligne 22 en remplaçant if (!*--p) par if (!(q = *--p)). Ensuite tous les (*p) deviennent des q.

Dernière remarque : je m’interroge sur la ligne 27. C’est elle qui fait sortir de la boucle while (1). La boucle for qui précède semble visiblement rechercher parmi les struct task_struct celle qui a la plus forte valeur pour le champ counter, dont la position est mémorisée dans next. On pourrait s’attendre à ce qu’on sorte du while(1) si on a trouvé un tel maximum, mais ça ne correspond pas au test qui est fait. Si on ne trouve pas de maximum on sort avec c == -1 et next == 0, comme -1 n’est pas 0 il est considéré comme vrai et donc on sort du while(1) dans ce cas. Le seul cas ou on ne sort pas est celui ou on trouve un maximum mais qu’il se trouve être zéro. C’est peut-être impossible si le champ counter n’est jamais nul mais si c’est le cas le test ne sert à rien et on n’exécute jamais le for qui suit. A mon humble avis le test devrait être soit if (c != -1) soit if (next) puisque next est initialisé à 0 et que si on trouve un maximum on sort avec sa position du while (--i) qui ne peut être 0 vu ce while (--i).

LB

Une fois que tes explications m’eurent donné les noms des choses, je les ai effectivement trouvées dans les livres, notamment au paragraphe 7.15 de l’appendice A de K&R, « L’opérateur virgule ». Et j’ai ainsi compris que c’était même une bonne construction, qui permet de faire la même chose que la forme « do » des Lisp modernes, comme exposé par MM. Gabriel et Steele Jr.

Quant à l’auteur du programme dont nous parlions, ce ne peut être, à cette époque, que Linus Torvalds.

RE

Ben Linus Torvalds se passera de mes compliments. Indépendamment des inélégances, il semble qu’il y ait avec le test de la ligne 27 une vraie erreur de logique dans cette fonction.

LB

Ce qui me gêne dans cette histoire, ce n’est pas l’opérateur virgule en lui-même, c’est que ce soit si mal documenté, et que cela devienne du coup un caractère confus du langage. Certes, aucune grammaire n’empêchera jamais d’écrire des programmes cryptiques, faux, moches ou les trois à la fois. Quand trop de choses sont laissées au gré d’options par défaut, on finit par ne plus savoir très bien ce qui est écrit, parce qu’il ne suffit pas de pouvoir écrire, mais il faut aussi lire le code d’autrui.

RE

Les options par défaut sont le pain béni des concurrents d’obfuscated C. Un bon exemple qui revient souvent est d’écrire i; au lieu de int i; ce qui marche parce que le type peut être omis dans une déclaration et le type par défaut est int.

Le nécessité de pouvoir lire le code d’autrui est mon principal argument contre C++ et de façon plus générale l’orienté objet. Même si on peut leur trouver des tas de vertus, le fait est que l’intention du programmeur est rarement évidente au vu du seul code dans ce type de langage. Si le programmeur ne met pas la dose de commentaires adéquate, c’est incompréhensible.

Ca me rappelle un dialogue entre un de mes collègues enseignant en informatique avec un étudiant dont le programme ne marchait pas :

- Monsieur je ne comprends pas, mon programme ne marche pas et je ne vois pas pourquoi
- Ah mais moi je vois tout de suite pourquoi il ne marche pas, c’est parce qu’il n’ y a pas de commentaires.
- Vous rigolez ?
- Mais non, pas du tout, mettez de commentaires et vous verrez, ça va marcher.
-  ??

L’étudiant s’exécute et bien entendu trouve son erreur en la commentant.

LB

Les « affectations condensées » me gênent aussi :

  1. i += 2
  2. var <<= 3

Télécharger

Bon, là aussi il y a des circonstances où c’est élégant...

RE

Et même indispensable. Cette construction a l’avantage de mettre l’accent sur le fait que le résultat est aussi l’un des deux opérandes, ce qui n’est pas un hasard. Il évite la bête erreur faute de frappe du doigt qui dérape et qui écrit j = k +1 au lieu de k = k+ 1. Par ailleurs, en C comme dans de nombreux autres langages, ce qui est à droite de l’affectation peut être une expression complexe qu’il est inutile de calculer 2 fois, voire même nuisible si ce calcul a un effet de bord qu’on ne veut pas répéter. On peut argumenter qu’un compilateur optimiseur évitera le recalcul, mais il ne le fera justement que s’il peut (se) prouver qu’il n’y a pas d’effet de bord. Et de toute façon je trouve inélégant d’écrire deux fois cette même expression.

Cette construction est l’équivalent en programmation itérative de ce qu’on fait fréquemment en programmation récursive : rappeler la fonction courante avec un argument qui est une fonction de celui qu’on a reçu en cette position, typiquement en LISP une fonction qui reçoit comme argument une liste L et qui la parcourt en se rappelant récursivement avec (CDR L).

A noter que cette construction est présente en Algol68 qui a influencé les auteurs de C. On trouve même dans les premiers Unix des progammes « Algolisés » à grand coups de #define (je crois que l’auteur est Bourne) :

  1. #define IF if(
  2. #define THEN ){
  3. #define ELSE }else{
  4. #define FI }

Télécharger

 Supplément : les en-têtes

Voici le fichier d’en-tête include/linux/sched.h, avec même un peu d’assembleur :

  1. #ifndef _SCHED_H
  2. #define _SCHED_H
  3.  
  4. #define NR_TASKS 64
  5. #define HZ 100
  6.  
  7. #define FIRST_TASK task[0]
  8. #define LAST_TASK task[NR_TASKS-1]
  9.  
  10. #include <linux/head.h>
  11. #include <linux/fs.h>
  12. #include <linux/mm.h>
  13.  
  14. #if (NR_OPEN > 32)
  15. #error "Currently the close-on-exec-flags are in one word, max 32 files/proc"
  16. #endif
  17.  
  18. #define TASK_RUNNING 0
  19. #define TASK_INTERRUPTIBLE 1
  20. #define TASK_UNINTERRUPTIBLE 2
  21. #define TASK_ZOMBIE 3
  22. #define TASK_STOPPED 4
  23.  
  24. #ifndef NULL
  25. #define NULL ((void *) 0)
  26. #endif
  27.  
  28. extern int copy_page_tables(unsigned long from, unsigned long to, long size);
  29. extern int free_page_tables(unsigned long from, long size);
  30.  
  31. extern void sched_init(void);
  32. extern void schedule(void);
  33. extern void trap_init(void);
  34. extern void panic(const char * str);
  35. extern int tty_write(unsigned minor,char * buf,int count);
  36.  
  37. typedef int (*fn_ptr)();
  38.  
  39. struct i387_struct {
  40. long cwd;
  41. long swd;
  42. long twd;
  43. long fip;
  44. long fcs;
  45. long foo;
  46. long fos;
  47. long st_space[20]; /* 8*10 bytes for each FP-reg = 80 bytes */
  48. };
  49.  
  50. struct tss_struct {
  51. long back_link; /* 16 high bits zero */
  52. long esp0;
  53. long ss0; /* 16 high bits zero */
  54. long esp1;
  55. long ss1; /* 16 high bits zero */
  56. long esp2;
  57. long ss2; /* 16 high bits zero */
  58. long cr3;
  59. long eip;
  60. long eflags;
  61. long eax,ecx,edx,ebx;
  62. long esp;
  63. long ebp;
  64. long esi;
  65. long edi;
  66. long es; /* 16 high bits zero */
  67. long cs; /* 16 high bits zero */
  68. long ss; /* 16 high bits zero */
  69. long ds; /* 16 high bits zero */
  70. long fs; /* 16 high bits zero */
  71. long gs; /* 16 high bits zero */
  72. long ldt; /* 16 high bits zero */
  73. long trace_bitmap; /* bits: trace 0, bitmap 16-31 */
  74. struct i387_struct i387;
  75. };
  76.  
  77. struct task_struct {
  78. /* these are hardcoded - don't touch */
  79. long state; /* -1 unrunnable, 0 runnable, >0 stopped */
  80. long counter;
  81. long priority;
  82. long signal;
  83. fn_ptr sig_restorer;
  84. fn_ptr sig_fn[32];
  85. /* various fields */
  86. int exit_code;
  87. unsigned long end_code,end_data,brk,start_stack;
  88. long pid,father,pgrp,session,leader;
  89. unsigned short uid,euid,suid;
  90. unsigned short gid,egid,sgid;
  91. long alarm;
  92. long utime,stime,cutime,cstime,start_time;
  93. unsigned short used_math;
  94. /* file system info */
  95. int tty; /* -1 if no tty, so it must be signed */
  96. unsigned short umask;
  97. struct m_inode * pwd;
  98. struct m_inode * root;
  99. unsigned long close_on_exec;
  100. struct file * filp[NR_OPEN];
  101. /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
  102. struct desc_struct ldt[3];
  103. /* tss for this task */
  104. struct tss_struct tss;
  105. };
  106.  
  107. /*
  108.  * INIT_TASK is used to set up the first task table, touch at
  109.  * your own risk!. Base=0, limit=0x9ffff (=640kB)
  110.  */
  111. #define INIT_TASK \
  112. /* state etc */ { 0,15,15, \
  113. /* signals */ 0,NULL,{(fn_ptr) 0,}, \
  114. /* ec,brk... */ 0,0,0,0,0, \
  115. /* pid etc.. */ 0,-1,0,0,0, \
  116. /* uid etc */ 0,0,0,0,0,0, \
  117. /* alarm */ 0,0,0,0,0,0, \
  118. /* math */ 0, \
  119. /* fs info */ -1,0133,NULL,NULL,0, \
  120. /* filp */ {NULL,}, \
  121. { \
  122. {0,0}, \
  123. /* ldt */ {0x9f,0xc0fa00}, \
  124. {0x9f,0xc0f200}, \
  125. }, \
  126. /*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
  127. 0,0,0,0,0,0,0,0, \
  128. 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
  129. _LDT(0),0x80000000, \
  130. {} \
  131. }, \
  132. }
  133.  
  134. extern struct task_struct *task[NR_TASKS];
  135. extern struct task_struct *last_task_used_math;
  136. extern struct task_struct *current;
  137. extern long volatile jiffies;
  138. extern long startup_time;
  139.  
  140. #define CURRENT_TIME (startup_time+jiffies/HZ)
  141.  
  142. extern void sleep_on(struct task_struct ** p);
  143. extern void interruptible_sleep_on(struct task_struct ** p);
  144. extern void wake_up(struct task_struct ** p);
  145.  
  146. /*
  147.  * Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall
  148.  * 4-TSS0, 5-LDT0, 6-TSS1 etc ...
  149.  */
  150. #define FIRST_TSS_ENTRY 4
  151. #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
  152. #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
  153. #define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
  154. #define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
  155. #define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
  156. #define str(n) \
  157. __asm__("str %%ax\n\t" \
  158. "subl %2,%%eax\n\t" \
  159. "shrl $4,%%eax" \
  160. :"=a" (n) \
  161. :"a" (0),"i" (FIRST_TSS_ENTRY<<3))
  162. /*
  163.  * switch_to(n) should switch tasks to task nr n, first
  164.  * checking that n isn't the current task, in which case it does nothing.
  165.  * This also clears the TS-flag if the task we switched to has used
  166.  * tha math co-processor latest.
  167. */
  168.  
  169. #define switch_to(n) {\
  170. struct {long a,b;} __tmp; \
  171. __asm__("cmpl %%ecx,_current\n\t" \
  172. "je 1f\n\t" \
  173. "xchgl %%ecx,_current\n\t" \
  174. "movw %%dx,%1\n\t" \
  175. "ljmp %0\n\t" \
  176. "cmpl %%ecx,%2\n\t" \
  177. "jne 1f\n\t" \
  178. "clts\n" \
  179. "1:" \
  180. ::"m" (*&__tmp.a),"m" (*&__tmp.b), \
  181. "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
  182. }
  183.  
  184. #define PAGE_ALIGN(n) (((n)+0xfff)&0xfffff000)
  185.  
  186. #define _set_base(addr,base) \
  187. __asm__("movw %%dx,%0\n\t" \
  188. "rorl $16,%%edx\n\t" \
  189. "movb %%dl,%1\n\t" \
  190. "movb %%dh,%2" \
  191. ::"m" (*((addr)+2)), \
  192. "m" (*((addr)+4)), \
  193. "m" (*((addr)+7)), \
  194. "d" (base) \
  195. :"dx")
  196.  
  197. #define _set_limit(addr,limit) \
  198. __asm__("movw %%dx,%0\n\t" \
  199. "rorl $16,%%edx\n\t" \
  200. "movb %1,%%dh\n\t" \
  201. "andb $0xf0,%%dh\n\t" \
  202. "orb %%dh,%%dl\n\t" \
  203. "movb %%dl,%1" \
  204. ::"m" (*(addr)), \
  205. "m" (*((addr)+6)), \
  206. "d" (limit) \
  207. :"dx")
  208.  
  209. #define set_base(ldt,base) _set_base( ((char *)&(ldt)) , base )
  210. #define set_limit(ldt,limit) _set_limit( ((char *)&(ldt)) , (limit-1)>>12 )
  211.  
  212. #define _get_base(addr) ({\
  213. unsigned long __base; \
  214. __asm__("movb %3,%%dh\n\t" \
  215. "movb %2,%%dl\n\t" \
  216. "shll $16,%%edx\n\t" \
  217. "movw %1,%%dx" \
  218. :"=d" (__base) \
  219. :"m" (*((addr)+2)), \
  220. "m" (*((addr)+4)), \
  221. "m" (*((addr)+7))); \
  222. __base;})
  223.  
  224. #define get_base(ldt) _get_base( ((char *)&(ldt)) )
  225.  
  226. #define get_limit(segment) ({ \
  227. unsigned long __limit; \
  228. __asm__("lsll %1,%0\n\tincl %0":"=r" (__limit):"r" (segment)); \
  229. __limit;})
  230.  
  231. #endif

Télécharger

 Post-scriptum : pourquoi je n’aime pas C

Finalement, cette conversation m’aura aidé à mieux comprendre pourquoi je n’aimais pas le langage C : toujours dans le souci d’économiser les frappes au clavier, ses auteurs ont substitué aux mots des langages « à la Algol » des signes cabalistiques, ce qui ne pose sans doute que peu de problèmes à ceux qui en usent quotidiennement, mais qui sont une torture pour les utilisateurs occasionnels tels que moi.

Plus grave et plus profond : les règles syntaxiques facultatives et les options par défaut, qui permettent d’écrire à peu près n’importe quoi, avec un résultat au petit bonheur la chance. Comme l’écrit Gérard Berry dans son livre L’hyperpuissance de l’Informatique, « C n’est pas un langage bien défini. Dans son standard, on voit en effet qu’il y a un grand nombre de comportements non spécifiés, voire indéfinis, qui ne peuvent donc pas être formalisés. »

Allez, tant qu’à passer pour un dinosaure, autant y aller carrément : vive Ada !

Notes :

[1Bon, le vrai Fortran d’origine, pas les succédanés décadents modernes, notait l’égalité .EQ., mais le laxisme n’a pas tardé à s’instaurer...

[2Philippe, Anne-Charlotte. « Quand une doctorante fait des heures supplémentaires ». 1024 – Bulletin de la société informatique de France, no Hors-série numéro 1 (2015).

[3Kamp, Poul-Henning. « The Most Expensive One-byte Mistake », 2011.

[4Tanenbaum, Andrew, et Nick Cook. « Minix 3.2.1, Scheduler main routine », 2017.

[5Torvalds, Linus. « Linux v0.01 Scheduler », 2017.

[6Roberson, Jeffrey, et Jake Burkholder. « FreeBSD 11.1 Scheduler », 2017.

[7Cegielski, Patrick. Conception de systèmes d’exploitation – Le cas Linux. Paris : Eyrolles, 2003.

[8Dans le contexte de Linux v0.01 tâche est synonyme de processus. Ultérieurement il peut y avoir une certaine confusion entre processus, tâche et thread, mais il s’agit toujours d’un programme candidat à l’exécution auquel le scheduler doit décider de donner ou non la main. La valeur de la variable jiffies est le nombre de tops d’horloge depuis le démarrage du système.

[9Salus, Peter H. A Quarter Century of UNIX. Reading, Massachusetts : Addison-Wesley, 1994.


Forum
Répondre à cet article
Que penser du langage C ?
B. - le 10 novembre 2017

Bonjour,

Je suis assez surpris des critiques que vous faites au C, langage assez critiquable certes mais pas forcément pour les raisons que vous mettez en avant ! En particulier, votre remarque sur le symbole d’affectation me laisse un peu pantois.

D’une part, on peut voir sur Wikipedia par exemple que "=" un symbole très largement utilisé pour l’affectation, pas moins que " :=". Bien sûr la majorité n’a pas forcément raison, mais le fait que de nombreux langages, dont un certain nombre postérieurs au C, fassent ce choix laisse tout de même penser que ce n’est pas une mauvaise idée du C dont il faudrait se débarrasser.

L’autre argument contre le signe "=" pour l’affectation est la confusion avec le signe "=" en mathématiques, argument que j’ai souvent entendu et qui me chagrine un peu. Le signe "=" en mathématiques a fondamentalement deux significations ! Quand on écrit "soit x = p/q ..." (dans une démonstration disons), on l’utilise bien pour l’affectation. Et quand ensuite on écrit "donc x = p/q", on utilise son sens d’égalité. Si ambiguïté il doit y avoir, elle vient des mathématiques. Au contraire, l’informatique rend explicite ce double sens. Il faut donc, dans un soucis de clarté, associer deux symboles distincts pour ces deux sens. Ensuite, choisir de remplacer le "=" de l’affectation par " :=" (ou "<-" voire "←") ou le "=" de l’égalité par "==" ne me semble pas fondamentalement différent !

Que penser du langage C ?
Laurent Bloch - le 10 novembre 2017

L’énoncé mathématique « Soit x = p/q ... » ne correspond pas à l’affectation informatique (en Scheme « (set ! x (/ p q)) ») mais très précisément à l’établissement, dans le contexte considéré, d’une liaison entre le symbole x et une valeur, soit en Scheme : (let ((x (/ p q))) ... )

On peut toujours utiliser les symboles que l’on veut, le problème, pour l’enseignement aux débutants, est qu’ils ont appris depuis l’école primaire que « = » était le symbole de l’égalité, et que l’utiliser pour l’affectation est source d’incompréhensions et de confusions dont j’ai fait de nombreuses fois l’expérience, d’autant plus que les deux notions ne sont pas sans relations mutuelles. Et que tous les langages qui ont succédé à C aient repris la même voie est plus un phénomène à soumettre à des sociologues, voire des psychologues, qu’un argument positif.

Que penser du langage C ?
Laurent Bloch - le 11 novembre 2017

À propos du concept d’égalité, je conseille vivement cet article de David Monniaux :

http://david.monniaux.free.fr/dotclear/index.php/post/2017/11/01/L-%C3%A9galit%C3%A9%2C-concept-plus-difficile-qu-il-n-y-para%C3%AEt

Que penser du langage C ?
Robert Ehrlich - le 13 novembre 2017

A l’école primaire, il n’ont pas appris que "=" désigne l’égalité, ils ont appris que c’est la même chose que "font", 2 plus 2 "font" 4. Le "=" de l’école primaire, c’est un peu (mais pas tout à fait) celui des calculettes, qui veut dire "dis-moi combien ça fait".

Le "=" du C n’est ni celui de l’école primaire, ni celui des mathématiciens, c’est normal, on est dans un autre domaine où les mots et symboles utilisés ailleurs peuvent avoir un autre sens, ce n’est pas inusuel. La réflexion de la collégienne devant x = x + 1 est du même tonneau que celui qui, lisant pour la première fois dans une recette de cuisine "faire revenir les oignons" s’étonnerait : "mais ils ne sont pas partis".

L’argument de brièveté pour l’emploi d’un symbole court me semble tout à fait recevable, c’est comme ça que fonctionnent toutes les langues naturelles, évoluant au besoin pour que les termes devenant plus fréquents deviennent plus courts. Ainsi le récepteur de télévision est devenu "la télé", ce qui n’empêche pas de comprendre que le télétravail n’a rien à voir avec ça.

Le := n’est pas meilleur en tout cas, il est parfois employé en maths pour dire "égal par définition". Si j’avais à refaire le langage, j’opterais plutôt pour le "@", en échangeant l’ordre des opérandes, à gauche ce qu’on calcule, à droite là ou on le met. Ou bien le symbole "<-", comme en APL, en gardant l’ordre, mais en sacrifiant la brièveté.

D’ailleurs LISP et ses dérivés ont aussi évolué dans le sens de la brièveté, PLUS, MINUS, etc. sont devenus +, -, etc. tout en conservant éventuellement les équivalents anciens.

De toute façon il faut bien insister pour les débutants sur le fait que certains symboles utilisés qu’on emploie en mathématique n’ont pas le même sens. Déjà sur le plans général, en programmation il s’agit de faire quelque chose et non d’affirmer quelque chose. Mais plus précisément des symboles bien plus familiers que celui de l’affectation n’ont pas (tout à fait) le même sens qu’en mathématique. Ainsi l’addition désignée par "+" n’est pas tout à fait celle des mathématiques. Pour les entiers elle se fait la plupart du temps modulo une puissance de 2. Pour les "flottants" elle est approximative, avec comme conséquence qu’elle n’est plus associative. Corollaire : une somme de nombreux termes dépend de l’ordre dans lequel on les additionne. Corollaire du précédent : une série dont le terme général tend vers 0 converge toujours si on la calcule dans l’ordre "naturel" ; en effet le terme général finit toujours par devenir suffisamment petit pour que son addition à la somme des précédents ne change rien, à partir de là cette somme devient constante. Leçon à en tirer : si on approxime quelque chose par une série, d’abord calculer le nombre de termes qu’il faut pour avoir l’approximation voulue, ensuite les sommer en partant de la fin pour que l’effet cumulé des plus petits ait plus de chances de se faire sentir sur les plus gros.

Que penser du langage C ?
Laurent Bloch - le 14 novembre 2017

La qualité et la subtilité des arguments ne m’échappent pas, ils sont plus consistants que ce que l’on entend d’habitude sur ce sujet, mais je ne m’y range pas pour autant.

Qu’un langage soit adapté à l’enseignement suppose qu’il soit expressif et que chaque expression ne soit ni un rébus ni une devinette. Et comme on ne cesse d’apprendre, ce qui vaut pour l’enseignement vaut pour la pratique professionnelle. Évidemment, pour qui est friand de rébus et de devinettes, C est parfait, et C++ encore mieux, mais je crains que ce ne soient divertissements pour adolescents.

Que penser du langage C ?
B. - le 17 novembre 2017

Je ne comprends pas entièrement votre raisonnement. Je veux bien discuter le fait que le symbole "=" de "Soit x = p/q" ne soit pas une affectation, mais ce n’est pas non plus un test d’égalité ! C’est une façon, comme vous le dites, de lier la symbole x à la valeur p/q. Il a donc une toute autre nature que le même symbole dans "donc x = p/q". Je trouve que l’informatique permet de faire comprendre cette distinction.

J’aurais dû le préciser dès mon premier commentaire, mais mon étonnement et ma remarque viennent du fait que je n’ai pas du tout l’expérience que vous avez vis-à-vis des programmeurs débutants. J’ai beau enseigner tous les ans depuis un certain nombre d’années l’introduction à la programmation, ce qui m’a permis de voir passer des cargaisons de débutants, je n’ai jamais relevé une particulière difficulté concernant l’affectation notée par =. Qu’elle soit notée =, :=, let ou que sais-je, je n’ai pas remarqué de différence chez les étudiants. La construction "x reçoit la valeur x + 1" (avec le symbole que vous voulez pour "reçoit la valeur") a toujours posé problème à quelques étudiants, et cette légère difficulté est vite surmontée. Cependant, je ne remets pas en cause votre expérience, et je serai attentif la prochaine fois que j’aurai à enseigner l’affectation avec le symbole "=", pour me rendre compte s’il n’y a pas effectivement une difficulté particulière venant du symbole utilisé que je n’aurais simplement pas su déceler jusqu’à maintenant.

Que penser du langage C ?
Laurent Bloch - le 18 novembre 2017

Il me semble que le problème gît justement là : les étudiants acceptent sans difficulté une notation beaucoup plus équivoque qu’il n’y paraît. Comme dans ma génération nous acceptions sans broncher la démonstration des cas d’égalité des triangles héritée d’Euclide, alors que depuis déjà belle lurette Hilbert avait montré que ce n’était pas une démonstration. De cette acceptation initiale trop facile découlent des erreurs à venir. Pour cette question d’égalité, je renvoie au texte de David Monniaux

À l’époque où mes propres idées sur le sujet étaient inexistantes, je ne me posais pas de questions, mais lorsqu’un collègue eut attiré mon attention sur cette distinction entre affectation et égalité, j’ai compris rétrospectivement que cette confusion avait été à l’origine de beaucoup de mes erreurs antérieures. Alors autant éviter ces divagations dès le départ.



pucePlan du site puceContact puceEspace rédacteurs puce

RSS

2004-2017 © Site WWW de Laurent Bloch - Tous droits réservés
Site réalisé sous SPIP
avec le squelette ESCAL-V3
Version : 3.87.47