Editeur de texte en C

@ all :
Voila histoire de pas rester sans rien faire pendant les vacances, je me suis demande comment faire un éditeur de texte tout bete en C. Donc interface en ncurses, jusque la rien de bien complexe, avec detection des touches et remplissage d’un buffer. Quand le buffer est plein on le sauve dans un fichier et on recommence etc. Pour ce qui est de la lecture dans le fichier, on scanne le contenu du fichier a ouvrir et on charge une partie du contenu dans le buffer. Bref, jusque la rien de sorcier. Toujours est il que j’ai commence a faire ca et je vous poste le resultat. Resultat ca marche pour le moment, mais en cours d’utilisation, la CPU a des facheuses tendances a “swinger” autour de 60% - 90%. Ca fait quand meme beaucoup pour un simple editeur de texte en console nan :p. Donc j’aimerais avoir vos suggestions quant aux ameliorations possibles surtout sur ce point. Sur d’autres aussi bien evidemment.

A oui autre remarque, savoir si quelqu’un aurait une approche differente pour la maniere d’envisager le probleme, ca c’est pour la culture et savoir si le code est comprehensible et propre, ca c’est pour la critique :stuck_out_tongue: (Nan ca m’aiderait de savoir si je code potablement ou pas sachant que je suis en ecole d’inge et que je me destine a faire de l’embarque, domaine dans lequel on utilise tout plein de C, avec du C proche du materiel (la c’est pas le cas dans l’exemple, mais si le code est deja propre ca n’en est que mieux).

J’attends vos remarques a tous. Merci.

Fichier editor.h

 * Simple text editor by Doby
 */

#ifndef EDITOR_H
#define EDITOR_H

/*
 * Define buffer size and line size (width)
 */
#define length 800
#define width 80

/*
 * Define values of ASCII keys
 */
#define ENTER 10
#define ESCAPE 27



/*
 * InitBuffer(char buffer[length])
 * All cells of the buffer contain '\0' caracter
 */
void InitBuffer(char buffer[length]);



/*
 * UseBuffer(char buffer[length])
 * Move the cursor in the buffer + Insert & Modify content of the buffer
 */
void UseBuffer(char buffer[length]);



/*
 * Print buffer
 */
void PrintBuffer(char buffer[length]);



/*
 * Quit Editor
 */
void Quit();

#endif //EDITOR_H

Fichier editor.c

[code]#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
#include “…/Include/editor.h”

volatile int Position;

int main(void){
char buffer[length];
int index = 0;
Position = 0;

//Initialisation Graphique
initscr();
cbreak();
curs_set(0);
keypad(stdscr,TRUE);

//Initialisation buffer
InitBuffer(buffer);
PrintBuffer(buffer);

while(1){
UseBuffer(buffer);
usleep(10000);
}

//Retour programme
endwin();
system(“clear”);
exit (0);
}

/*

  • All cells of the buffer contain ‘\0’ caracter
    */
    void InitBuffer(char buffer[length]){
    int index = 0;
    for(index = 0; index < length; index++){
    buffer[index] = ’ ';
    }
    }

/*

  • Move cursor in the buffer + Insert & Modify content of the buffer
    */
    void UseBuffer(char buffer[length]){
    int key;
    nodelay(stdscr, TRUE);

if((key = getch())== -1)
return;

else{
switch(key){
case KEY_UP:
if(Position > width)
Position -= width;
break;

case KEY_DOWN:
  if(Position < length - width - 1)
  Position += width;
  break;
  
case KEY_LEFT:
  if(Position > 0)
Position--;
  break;

case KEY_RIGHT:
  if(Position < length - 1)
Position++;
  break;

case KEY_BACKSPACE:
  buffer[Position] = ' ';
  if(Position > 0)
Position--;
  break;
  
case KEY_HOME:
  Position -= (Position % width);
  break;

case ENTER:
  if(Position < length - width - 1)
Position = (Position + width - (Position % width));
  break;

case ESCAPE:
  Quit();
  break;
  
default:
  if(key >= 32 && key <= 126){
buffer[Position] = (char)key;
if(Position < length - 1)
  Position++;
  }
  break;
}

//Call PrintBuffer
PrintBuffer(buffer);

}
}

/*

  • Print buffer
    */
    void PrintBuffer(char buffer[length]){
    int line = 0, column = 0, index = 0;

system(“clear”);

//Print content of the buffer
for(index = 0; index < length; index++){
line = (index / width);
column = (index - line * width);
if(index == Position)
mvaddch(line, column, ‘§’);
else
mvaddch(line, column, buffer[index]);

refresh();

}
}

/*

  • Quit Editor
    */
    void Quit(){
    endwin();
    system(“clear”);
    exit (0);
    }
    [/code]

Bon, comme ça ,en premier jet:

  • pourquoi volatile pour Position ? c’est plutôt pour des accès à des franges mémoire d’interface avec du matos (non relogeable), si je me souviens bien, cette classe de stockage là, non ?
  • il vaut mieux remplir un buffer avec de l’aléatoire(avec un zero en premier bien sûr), pour la sécurité, ça évite qu’on détecte en les lisant les zones mémoire écrites qui se distinguent avec les zones vierges remplies de zero. Quand on veut protèger et crypter une partition par exemple, on commence par la remplir de bruit plutot que de zeros.
  • le

while(1){ UseBuffer(buffer); usleep(10000); }
est une attente active, c’est de là je pense que vient ton surcroit d’activité: il vaut mieux accrocher un hook au clavier qui ne se déclenche qu’en cas de frappe d’une touche ( je ne sais pas faire ça je te le dis tout de suite) qu’aller régulièrement dans une boucle regarder s’il n’y a pas qqchose dans le buffer clavier (getchar).
Pour le reste…

Je suis pas un dieu et ce que je vais dire est peut être contestable, mais :

Toute première chose, une incroyable petite chose un truc con, très facile à régler, mais qu’on voit absolument partout!
Et qui a le dont de m’exaspérer :

Ça c’est pas très beau, quand tu n’utilise pas ce qui est renvoyé par la postincrémentation utilise toujours la préincrémentation. Pour faire une post-incrémentation il doit copier la variable, l’incrémenter puis renvoyer la copie. C’est pas grand chose mais dans l’embarqué ça peut jouer. gcc corrige l’erreur mais c’est pas une raison.

Quand tu vois un test avec un 0 dedans pose toi la question de revoir ta condition et préfère ça

à

ou

si var est unsigned.
De la même manière je crois que le modulo est calculé de manière très lourde, il vaut mieux faire :

que

J’ai aussi tendance à écrire :

plutôt que

La fonction

void InitBuffer(char buffer[length]){ int index = 0; for(index = 0; index < length; index++){ buffer[index] = ' '; } }
pourrais devenir

void InitBuffer(char buffer[length]){ for(int index = 0; index < length; ++index){ buffer[index] = ' '; } }
non?

Tu utilise systèmatiquement les -= += /= etc c’est bien, le processeur utiliseras 2 variables au lieux de 3 je crois.

Sinon je suis d’accord avec mattotop :wink:

Bien sur tout ce que je dis ne tiens pas compte des optimisation du compilateur :slightly_smiling:

Merci pour les remarques :slightly_smiling:
Pour le volatile, c’est parce que j’avais besoin d’y acceder tout le temps donc en premiere approche j’avais fais ca “a l’arrache” :slightly_smiling:. La j’ai fait une structure directement avec le buffer et l’int pour la position, ce qui est ma fois nettement plus propre. Ensuite, j’ai mis le tout dans une “vrai” fenetre ncurses (la pour le moment c’etait juste dans la fenetre genere par initscr(), mais je ne sais pas si ca change beaucoup de chose. Puis j’ai mis des \0 pour l’init du buffer (d’ailleurs Matt pourquoi mettre de l’aleatoire?? Des \0c’est pas conseille ?). Pour la postincrementation vs la preincrementation, j’avoue que c’est plus par habitude que je fait var++ que ++var. Mais Yoko ta remarque est parfaitement justifiee, c’est vrai que dans mes cours de C de l’an dernier le prof en parlait de maniere explicite. Sinon pour la detection des touches, il faut que je continue de rechercher une solution plus elegante parce que celle la est effectivement tres lourde au niveau memoire et d’autant plus longue que le buffer est long (ca je vois pas trop pourquoi ca c’est du a l’affichage apres chaque appui sur une touche…). Il faut que je me penche sur kbhit et son utilisation, voir recoder cette fonction qui est dispo sous Win**** mais pas sous Tux.
A si juste une petite remarque pour le -= et += ouais c’est peut etre moins gourmand en memoire mais c’est un peu moins propre :slightly_smiling: (enfin personnelement c’est ce que je me dis en voyant ca).

quel message je supprime dans ce doublon ? :laughing:

quote="Doby"Puis j’ai mis des \0 pour l’init du buffer (d’ailleurs Matt pourquoi mettre de l’aleatoire?? Des \0c’est pas conseille ?).(…)[/quote]Comme je le disais, c’est un détail de sécurité, mais si un type scanne la mêmoire ou un fichier de swap, il lui sera plus facile de détecter les zones inutilisées remplies de \0 et de regarder avant ce qu’il peut récupèrer de ce qu’il y avait dans le buffer, alors que si c’est de l’aleatoire, il sera obligé de regarder si ce qu’il y a avant chaque \0 est bien un contenu de buffer reconnaissable, ou si le 0 se trouve là par hasard.
Pour l’attente active, j’ai dit n’importequoi, puisque le getch est bloquant.

En voici une implémentation en c++, tu peut t’en inspirer :
http://www.cppfrance.com/code.aspx?ID=10611

Ça c’est une question de goût et d’expérience si je te disais ce que je pensais du printf, fprintf, etc… :wink:

l’engueule pas là dessus, il fait du getch. :laughing:

Loool. Bah Matt si tu pouvais supprimer le premier message ca serait cool. C’est juste que j’avais ecris “chnage” au lieu de “change” dans l’un et que j’avais corrige en direct donc ca a poster 2 fois erf.

Sinon j’ai trouve une implementation de kbhit voila ce que ca donne :

int kbhit(void)
{
   struct timeval tv = { 0, 0 };
   fd_set readfds;

   FD_ZERO(&readfds);
   FD_SET(STDIN_FILENO, &readfds);

   return select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) == 1;
}

Ca a legerement ameliore la fluidite de la detection des touches mais c’est pas encore optimise tout ca. Je vais voir ce que je peux encore ameliorer dessus et je posterais ce soir ce que ca donne.

J’utilise un autre compilateur, mais pour lui cela ne change rien (sans optimisation). J’essayerais avec d’autres pour voir, mais je ne pense pas que cela change quoi que ce soit.

Moi, j’utilise la première notation quand je travaille avec des booléens, qui ne peuvent être que OUI ou NON.

L’utilisation de % me paraît plus judicieuse car elle donne toujours le bon résultat :p!
Après, si il faut grignoter quelque part …

Le & vérifie la présence des bits mais en aucun cas l’égalité des deux variables.
L’égalité en assembleur peut se résumer à un ou exclusif suivit d’un branch si null.
Soit en binaire :
1001 ^ 1001 = 0000 -> BRANCH
1011 ^ 1001 = 0010
Du cou, il n’y a aucun intérêt à ne pas utiliser la notation == qui est plus claire.

[quote]
Tu utilise systèmatiquement les -= += /= etc c’est bien, le processeur utiliseras 2 variables au lieux de 3 je crois.

Bien sur tout ce que je dis ne tiens pas compte des optimisation du compilateur :slightly_smiling:[/quote]

Le problème est en fait de regarder ce que le compilateur génère sans optimisation pour que cela ait un sens.

[quote=“thialme”][quote=“Yoko”]var++;
Ça c’est pas très beau, quand tu n’utilise pas ce qui est renvoyé par la postincrémentation utilise toujours la préincrémentation. Pour faire une post-incrémentation il doit copier la variable, l’incrémenter puis renvoyer la copie. C’est pas grand chose mais dans l’embarqué ça peut jouer. gcc corrige l’erreur mais c’est pas une raison.
[/quote]J’utilise un autre compilateur, mais pour lui cela ne change rien (sans optimisation). J’essayerais avec d’autres pour voir, mais je ne pense pas que cela change quoi que ce soit.[/quote]
Quel est la différence de signification entre la post et la pré incrémentation d’après toi ? Dans la post la fonction renvoie la valeur de la variable qui est incrémenté après l’instruction. En fait c’est une copie qui est faite pour pouvoir renvoyer la variable en question. Donc ça utilise de toute manière plus d’espace (déclaration est initialisation d’une variable).

En C les booléens sont des nombres et tout nombre peut être considéré comme booléen 0 est faux alors que tout le reste est vrai.

[quote]L’utilisation de % me paraît plus judicieuse car elle donne toujours le bon résultat :p!
Après, si il faut grignoter quelque part …[/quote]
% c’est l’opération modulo donc le reste dans la division ce que fait aussi ma petite opération. Mais je crois qu’en cours j’ai vu que % utilise une fonction très lourde du processeur.

[quote]Le & vérifie la présence des bits mais en aucun cas l’égalité des deux variables.
L’égalité en assembleur peut se résumer à un ou exclusif suivit d’un branch si null.
Soit en binaire :
1001 ^ 1001 = 0000 -> BRANCH
1011 ^ 1001 = 0010
Du cou, il n’y a aucun intérêt à ne pas utiliser la notation == qui est plus claire.[/quote]
Oui désolé c’était l’inverse du ou exclusif.

Je n’ai jamais dis que je ne savais pas ce qu’était la post ou pré incrémentation, j’ai simplement dis, ou bien devrais-je dire, essayer de dire, que du point de vue de mon compilateur une ligne simple telle que var++ ou ++var est identique quant à son code généré. C’est pas moi qui l’invente, c’est ce que je constate avec mon compilateur sans aucune optimisation. Donc pas besoin de se prendre la tête à dire il faut écrire var++ ou ++var car cela devient la même chose, et moi j’ai toujours aimé écrire var++. Tu peux faire autrement cela ne me dérange pas.

Cela ne tient qu’à toi. Moi je considère un booléen comme vrai ou faux, une variable logique associée à du binaire. Les autres types sont pour moi des nombres et peuvent être >0 <0 !=0.
Je le vois comme cela et je le code comme cela. Cela me permet de mettre en évidence les variables booléennes dans les différents tests.

Je dis cela car ta formule est fausse. Et puis il faut être curieux et allez au dela de ce que les profs peuvent dire. C’est pas très difficile à vérifier.

[quote]

[quote]Le & vérifie la présence des bits mais en aucun cas l’égalité des deux variables.
L’égalité en assembleur peut se résumer à un ou exclusif suivit d’un branch si null.
Soit en binaire :
1001 ^ 1001 = 0000 -> BRANCH
1011 ^ 1001 = 0010
Du cou, il n’y a aucun intérêt à ne pas utiliser la notation == qui est plus claire.[/quote]
Oui désolé c’était l’inverse du ou exclusif.[/quote]

Et oui, on ne peux pas compter que sur des souvenirs de cours, il faut pratiquer un minimum, et faire des tests par soi-même. Si tu utilisais souvent cette méthode, cela t’aurais choqué au premier coup d’oeil. :slightly_smiling:

Désolé de ne pas coder 8h par jour en C/C++, je m’essaie aussi à d’autre langages ainsi qu’à d’autres domaine.
Excuses mes inexactitudes aussi (au grand dame j’ai oublié une multiplication que je suis mauvais, je ne fais vraiment que du théorique, c’est affolant. Vraiment l’IUT (tout du moins en première année) ne fais vraiment que du théorique. Vaudrais que je change de voie).

Pour ce qui est de la vérification oui je pourrais me prendre la tête à aller voir ce qui se passe au niveau assembleur, je trouve pour le moment que le jeu n’en vaut pas la chandelle.

Je voudrais aussi dire que pour quelqu’un qui cherche la lisibilité du code, tu devrais t’appuyer uniquement sur le C et pas descendre dans les méandre de la bête.

Edit : En effet il semblerais que dans mon environnement de programmation l’opération modulo soit aussi légère que l’expression corrigée. Ensuite je n’ai pas la chance de posséder la norme C si tu là tu pourras nous dire si ce comportement est standard ou non. S’il n’en est pas fais mention alors il faudras vérifier à chaque fois que tu change de compilateur.

Pour ce qui est de la post ou pre incrémentassions là aussi mon environnement l’optimise, mais c’est pas dis que ce soit le cas partout. T’inquiète pas un jour j’achèterais la norme et je lirais moi même comme ça tu seras content.

J’appuie a 200% les remarques de thialme!

% echo "int main(int argc, char **argv) { for (int i = 0; i < 5; i++) printf("coincoin\n"); return 0; }" >t.c; gcc -Wall t.c -o t
t.c: In function 'main':
t.c:1: error: 'for' loop initial declaration used outside C99 mode
t.c:1: warning: implicit declaration of function 'printf'
t.c:1: warning: incompatible implicit declaration of built-in function 'printf'
t.c:1: error: 'coincoinn' undeclared (first use in this function)
t.c:1: error: (Each undeclared identifier is reported only once
t.c:1: error: for each function it appears in.)

He oui, C99… Mauvaise idée le coup du int i en C :wink: C’est plutot C++ comme façon de faire

[quote=“thialme”][quote=“Yoko”]
Quel est la différence de signification entre la post et la pré incrémentation d’après toi ? Dans la post la fonction renvoie la valeur de la variable qui est incrémenté après l’instruction. En fait c’est une copie qui est faite pour pouvoir renvoyer la variable en question. Donc ça utilise de toute manière plus d’espace (déclaration est initialisation d’une variable).
[/quote]

Je n’ai jamais dis que je ne savais pas ce qu’était la post ou pré incrémentation, j’ai simplement dis, ou bien devrais-je dire, essayer de dire, que du point de vue de mon compilateur une ligne simple telle que var++ ou ++var est identique quant à son code généré. C’est pas moi qui l’invente, c’est ce que je constate avec mon compilateur sans aucune optimisation. Donc pas besoin de se prendre la tête à dire il faut écrire var++ ou ++var car cela devient la même chose, et moi j’ai toujours aimé écrire var++. Tu peux faire autrement cela ne me dérange pas.

[/quote]

En fait, ça dépend du contexte d’utilisation que gcc va faire d’une façon ou l’autre je crois :slightly_smiling:

[quote=“Yoko”]Désolé de ne pas coder 8h par jour en C/C++, je m’essaie aussi à d’autre langages ainsi qu’à d’autres domaine.
Excuses mes inexactitudes aussi (au grand dame j’ai oublié une multiplication que je suis mauvais, je ne fais vraiment que du théorique, c’est affolant. Vraiment l’IUT (tout du moins en première année) ne fais vraiment que du théorique. Vaudrais que je change de voie).

Pour ce qui est de la vérification oui je pourrais me prendre la tête à aller voir ce qui se passe au niveau assembleur, je trouve pour le moment que le jeu n’en vaut pas la chandelle.

Je voudrais aussi dire que pour quelqu’un qui cherche la lisibilité du code, tu devrais t’appuyer uniquement sur le C et pas descendre dans les méandre de la bête.

Edit : En effet il semblerais que dans mon environnement de programmation l’opération modulo soit aussi légère que l’expression corrigée. Ensuite je n’ai pas la chance de posséder la norme C si tu là tu pourras nous dire si ce comportement est standard ou non. S’il n’en est pas fais mention alors il faudras vérifier à chaque fois que tu change de compilateur.

Pour ce qui est de la post ou pre incrémentassions là aussi mon environnement l’optimise, mais c’est pas dis que ce soit le cas partout. T’inquiète pas un jour j’achèterais la norme et je lirais moi même comme ça tu seras content.[/quote]

Nan mais tu sais, une des choses les plus importantes dans l’informatique, c’est ta compréhension des choses, pas celle des autre. En d’autre termes, si tu sais pas comment fonctionne les rouages du C, tu as peu de chances de voir les implications que ça pourrait avoir sur ton java, ou ton python, ou que sais-je !!

De plus, c’est très intéressant de savoir que le langage C n’est pas bas niveau, mais bel et bien haut niveau ! L’intelligence de ce langage, comme presque tout autre langage compilé de haut niveau, est bien située dans le compilateur! Il est donc sain de se baser sur ce que sait faire un compilateur. De plus, on a un super compilateur libre qui s’appelle GCC et qui est porté sur toute les architecture que je connaisse… Pourquoi ne pas en profiter ? Autant coder en assembleur direct, la effectivement, tu pourra optimiser a la main :slightly_smiling:

A+,

En même temps, le C est plus bas niveau que du Java… :stuck_out_tongue: Dire que le C est du “haut niveau” ça me fait un peu bizarre ^^ C’est pas de l’assembleur, mais quand même. :slightly_smiling:

@ Pingoo Pat:
Le C est un langage assez basique, mais ca depend dans quoi tu bosses. Dans l’embarque par exemple, oui j’en parle parce que c’est un domaine qui me tient a coeur et donc pour lequel j’ai lu quelques docs :p, le C est considere comme etant le langage de “haut niveau” tandis que l’assembleur est considere comme le langage de “bas niveau”. Apres tu va pas t’amuser a mettre du java et toutes les dependances que ca apporte dans un composant qui n’as que 48K de memoire (ce qui est considere par certains comme le bout du monde :p). Tout est une question de point de vue.

Sinon pour vos remarques j’en tient compte, y a pas de probleme la dessus :slightly_smiling:. Le coup du for(int i …), personnellement ca me herisse un peu de voir ca. meme dans du C++ ou c’est autorise ca me “choque” un peu parce que c’est comme si on melangeait des declarations donc manipulation de memoire dans du code donc executable. Apres c’est a chacun de faire comme il veut je pense. Par contre je ne nie pas l’effet “pratique” de la chose a savoir je declare une variable la ou j’en ai besoin et uniquement la. C’est sur que point de vue reservation de memoire a la volee c’est mieux…

Je vais essayer de me remettre au code et de l’ameliorer parxce que j’avais reussi a epurer un peu “la bete”, mais avec l’ajout de fonctionnalite comme Chargement/Sauvegarde de fichier et une refonte de l’interface en ncurses c’est redevenu aussi lent. Erf.

de toutes les manières avec ce int i placé là ou il est comprehensible ou ailleurs, le compilateur optimise (même en -O minimal) pour n’allouer la mêmoire que quand il en a vraiment besoin, et les variables ne trainent pas en mêmoire. La notion de portée est purement syntaxique et ne se retrouve pas dans l’assembleur génèré à ma connaissance. Les autres niveaux d’optimisation peuvent à la limite faire des changements dans la classe de stockage et par exemple mettre une variable en register pour optimiser une boucle, mais si vous faites en deux instructions consécutives un x=y;z=x avec un x que vous n’utilisez pas ailleurs même le -O 2 va faire disparaitre x (sauf peut être si vous déclarez x en volatile).

Mais euh les mecs, réviser un peu. C c’est haut niveau. ya pas de “embarqué” ou pas.