Différences entre les versions de « Le C »
(Ajout conclusion) |
|||
(4 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 140 : | Ligne 140 : | ||
} mes_options; | } mes_options; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Ligne 183 : | Ligne 183 : | ||
Ajouter ce qui est, en surbrillance, dans le '''''casdecole.c''''': | Ajouter ce qui est, en surbrillance, dans le '''''casdecole.c''''': | ||
<syntaxhighlight lang="cpp" line start="1" highlight="2, | <syntaxhighlight lang="cpp" line start="1" highlight="2,5-18,23-29,36"> | ||
#include <stdio.h> | #include <stdio.h> | ||
#include "casdecole.h" | #include "casdecole.h" | ||
void opt_main(mes_options *opt) | |||
/* Notez que ce opt n'est pas global au code, ce montre qu'on peut utiliser les mêmes noms de variables dans tout le code sans avoir les désavantage d'une variable global, initilisée en dehors du main()... */ | |||
void opt_main(mes_options *opt) | |||
{ | { | ||
int count = 1; | int count = 1; | ||
Ligne 193 : | Ligne 196 : | ||
printf(“les arguments disponibles sont : \n”); | printf(“les arguments disponibles sont : \n”); | ||
/* Notez le opt-> utilisé à la place du point lors de l'utilisation de référence, on retrouve cette syntaxe dans le Perl avec a=() ou a={} ... */ | |||
for( ; count < opt->argc ; count++) | for( ; count < opt->argc ; count++) | ||
{ | { | ||
Ligne 213 : | Ligne 217 : | ||
/* La suite */ | /* La suite */ | ||
/* Utilisation de la référence du pointeur opt par le & */ | |||
opt_main(&opt); | opt_main(&opt); | ||
Ligne 246 : | Ligne 252 : | ||
<!> Ici l'emploi du compilateur C et C++ est volontaire (gcc et g++). Un compilateur C++ sait parfaitement compiler un projet en C pure. L'inverse n'est évidement pas possible pour des raisons historiques. | <!> Ici l'emploi du compilateur C et C++ est volontaire (gcc et g++). Un compilateur C++ sait parfaitement compiler un projet en C pure. L'inverse n'est évidement pas possible pour des raisons historiques. | ||
== Structures complexes == | == Structures complexes == | ||
Ligne 266 : | Ligne 271 : | ||
void (*function)(struct *mon_menu, char *, char *); | void (*function)(struct *mon_menu, char *, char *); | ||
mon_menu *next ; | |||
mon_menu *prev ; | |||
} command_tbl; | } command_tbl; | ||
typedef | typedef mon_menu ma_structure_menu | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Ligne 278 : | Ligne 283 : | ||
Personnellement, j'adore utiliser cette forme, car elle me donne l'impression d'avoir la puissance du C++, la gestion d'objet, alors qu'un compilateur C est utilisé... | Personnellement, j'adore utiliser cette forme, car elle me donne l'impression d'avoir la puissance du C++, la gestion d'objet, alors qu'un compilateur C est utilisé... | ||
=== Définition et attribution === | |||
à placer après la définition des prototypes du .H ou dans le .c : | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
/* Une autre façon de déclarer une structure dite ‘complexe’ */ | /* Une autre façon de déclarer une structure dite ‘complexe’ */ | ||
Ligne 285 : | Ligne 294 : | ||
char *nom; /* name of command */ | char *nom; /* name of command */ | ||
void (*function)(char *, char*, char*); /* pointer to function */ | void (*function)(char *, char*, char*); /* pointer to function */ | ||
int | int droits; | ||
} on_msg_commands[] = | } on_msg_commands[] = | ||
/* Commande function | /* Attributions... | ||
Commande function droits */ | |||
{ | { | ||
{ " | { "SELF", do_menu, 0}, | ||
{ " | { "HELP", show_help, 0}, | ||
/* | /* | ||
. | . | ||
: | : | ||
*/ | */ | ||
{ NULL, null(void(*)()), 0 | { NULL, null(void(*)()), 0 } | ||
}; | }; | ||
</syntaxhighlight> | |||
''(à placer dans le .c)'' | ''(à placer dans le .c)'' | ||
<syntaxhighlight lang="cpp"> | |||
void do_menu(mon_menu opt) | |||
{ | { | ||
/* fonction gérant les droits d'accès et exécution des argv */ | |||
} | } | ||
</syntaxhighlight> | |||
=== Arrêt des hostilités === | |||
Cette partie, ci-dessus, n'est hélas pas terminée d'être traitée dans ce tuto.<br/> | |||
Voici un aperçu de comment s'en servir mais sans explications, désolé : | |||
void | <syntaxhighlight lang="cpp"> | ||
void show_help(char *from, char *to, char *msg) | |||
{ | { | ||
/* ... */ | |||
} | } | ||
/* ce code doit-être placé dans une fonction ou main() ;) */ | |||
/* command, from, to msg sont de stype char * */ | |||
for(i = 0; on_msg_commands[i].name != NULL; i++) | |||
/* on vérifie que le nom, mais on pourrait filtrer sur des critères comme userlvl... */ | |||
if(STRCASEEQUAL(on_msg_commands[i].nom, command)) | |||
{ | |||
on_msg_commands[i].function(from, to, *msg?msg:NULL); | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Ligne 327 : | Ligne 350 : | ||
/* variable non accessible ailleur que dans ce fichier */ | /* variable non accessible ailleur que dans ce fichier */ | ||
static int var_protect = 198; | |||
</syntaxhighlight> | |||
'''extern''' déclare qu'une fonction est déclarée ailleurs que dans le fichier où elle est définie | '''extern''' déclare qu'une fonction est déclarée ailleurs que dans le fichier où elle est définie : | ||
<syntaxhighlight lang="cpp"> | |||
/* fonction déclarée ailleurs que dans ce fichier */ | /* fonction déclarée ailleurs que dans ce fichier */ | ||
extern void la_fonction(void); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Ligne 339 : | Ligne 364 : | ||
== Le bon "const" == | == Le bon "const" == | ||
const règle l'accès à une données en lecture seule. Cette donné peut-être un emplacement mémoire, un pointeur ou une référence… | |||
const char *p1 = "mon discours"; | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
char | char const *p2 = "mon discours 2"; | ||
char *p3 | char *p3 const = "mon discours 3"; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Ligne 363 : | Ligne 388 : | ||
Ceci pour avertir le développeur qu'un standard à l'autre, les projets peuvent ne pas se compiler sans employer le bon standard du moment où vous gérez votre projet... | Ceci pour avertir le développeur qu'un standard à l'autre, les projets peuvent ne pas se compiler sans employer le bon standard du moment où vous gérez votre projet... | ||
Donc mémorisez bien la norme par défault, ici. | Donc mémorisez bien la norme par défault, ici. | ||
== Conclusion == | |||
Travailler avec une structure donne l'avantage de tout placer, données et méthodes (fonctions), à l'intérieur du projet. | |||
Passer en argument cela évite tout problème lié aux variables globales, d'appelle de fonction... | |||
De plus, il devient plus facile de gérer la mémoire et sa libération avec free() quand c'est utile. | |||
Avec cette méthode, on peut donc considérer notre structure comme le pointeur ''this'' du C++ et l'ajouter dans chaque fonction. |
Version actuelle datée du 8 juin 2022 à 10:42
Avant-propos
Ce mini tuto est destiné aux programmeurs maîtrisant déjà un langage de programmation. Ce qui est proposé dans ce document est d’utiliser une structure (sa référence) comme argument de fonction et ainsi mettre en place un concept (que peut de gens se servent) proche de l’objet cher aux langage évolué comme le c++. Une approche très utile lorsque vous ne pouvez pas utiliser le C++, par exemple dans certains microcontrôleur, développement de drivers où le fabricant ne donne que des outils en C, le Kernel de Linux, ...
Cette façon de procéder a un autre avantage peu connu des programmeurs débutant. Il permet d'éviter des "dépassement mémoire" lors de passage d'arguments à d'autre fonction...
Il est donc question ici, de faire des appelles de fonction avec une structure et "sa référence".
Les données
Dans la grande majorité des cas, toutes informations transmises à un programme est stocké dans la mémoire de façon dynamique.
En C les valeurs qui peuvent être modifiées, dans la mémoire, sont représentées par des « variables » crées par des prototypes en tout genre…
Pour créer une variable dont le contenu peut-être changeant on fait appel, avant tout, à « la déclaration de variable » représenté ici en rouge :
int valeur_numerique ;
char caractere ;
char tableau_dim [1024] ;
char *pointeur ;
char **reference ;
/* Remplissage des variables et première utilisation : */
valeur_numerique = 65535 ;
caractere = 'c' ;
tableau_dim = "une phrase qui en vaut une autre" ; /* Ou chaine de caractère; Pouvant être utilisée tableau_dim[int count] */
pointeur = tableau_dim ;
reference = &pointeur ;
Fonctions et appel de fonction
Une fonction est un regroupement d’instructions pouvant être des conditions ou des changement d’état de la mémoire ( via les variables ou pointeurs).
Toutes ses instructions se placent dans un fichier ayant pour extension .c dans lequel peut y ajouter nos "définitions de fonctions".
Théorie : Définition de fonction simple :
Syntaxe :
prototype Nom1(void)
{
Instructions, fonctions ou méthodes c'est du kiff...
}
Vraie fonction à placer :
(à écrire dans un fichier nommé casdecole.c)
#include <stdio.h>
int main (int argc, char *argv[], char **env) /* **env équivalent à *env[] */
{
printf("Nombre d’argument disponible lors de l'appel du programme '%s' est égal à '%d'\n", argv[0] , argc);
/* La suite */
return(argc);
}
Transformer le code en "code machine" à l'aide du compilateur g++ Se dit aussi compiler :
g++ -o casdecole casdecole.c
La fonction main() est la seule obligatoire pour qu'un programme soit exécuté par le système. Sinon le résultat produit fournirait une librairie , voire ci-dessous.
Seul un code, avec main, pourrait s'en servir comme librairie (voyez là un dictionnaire d'instructions)...
Utilisation de librairies de fonctions
Une librairie est un magasin de fonctions organiser intelligemment pour faire gagner du temps au développement et permet au compilateur de perdre moins de temps lors d’une recompilation.
Pour être précis et rapide lorsque vous effectué des modifications dans un fichier de votre projet le compilateur va seulement recompiler le fichier modifié et non tout l’ensemble. Bien tendu il faut avoir organisé vos fonctions comme détaillé plus bas !
De façon générale il faut placer dans un fichier ayant pour extension '.h' (point H) tous les prototypes de fonction ainsi que les structures ; ceci permet au code d'être plus rapidement transformer dans les utilisations futures, mais pas seulement… Avec la commande unix "make" il peut savoir quel code a été modifié et lequel doit-être recompiler...
Une astuce fort utile dans un .h
Quelque soit l’utilisation que vous prévoyez de votre .h il faut prendre garde à ce qu’il ne soit pas relu par le compilateur afin d'éviter une erreur de compilation ou une mise en garde (warning) disant que certaines choses sont redéfinies ; ça fait mauvais genre et ça compile pas!
Pour éviter ce piège il faut placer les instructions à l'intérieur d'un #ifndef et #endif comme suit :
Fichier casdecole.h
#ifndef CAS_D_ECOLE /* Si CAS_D_ECOLE n’est pas définie (ou utilisé) j’interprète les instructions suivante, SINON celles qui suivent le #endif qui me concerne… */
#define CAS_D_ECOLE /* par abus de langage on pourrait dire que la variable CAS_D_ECOLE passe de l'état NULL à définie... */
/* Définition (ou alias/mini-macros ...) */
/* Inclusion de libraires ... */
/* Structures */
/* Prototypes ... */
#endif /* concerne le ifndef de CAS_D_ECOLE */
Structure simple
Une structure est un ensemble d'instructions servant principalement à diminuer le nombre d'argument à fournir à une fonction ou éviter les variables globales ; un fléau lorsqu'on veut modifier ou débugger son code…
Pensez à placer vos structures dans le .h, ici placer la vrai structure dans casdecole.h.
Syntaxe :
struct NOM
{
... Instructions ...
};
Pour de vrai :
(à placer dans la partie /* Structures... */)
typedef struct mes_options_structure
{
int argc;
char **argv;
char **env;
} mes_options;
code actuel
Si vous avez bien observé les deux code en .h ci-dessus, votre code actuel devrait ressembler à ceci casdecole.h:
#ifndef CAS_D_ECOLE /* Si CAS_D_ECOLE n’est pas définie (ou utilisé) j’interprète les instructions suivante, SINON celle qui suivent le #endif qui me concerne… */
#define CAS_D_ECOLE
/* Définition (ou alias/mini-macros ...) */
/* Inclusion de libraires ... */
/* Structures */
typedef struct mes_options_structure
{
int argc;
char **argv;
char **env;
};
/* Prototypes ... */
#endif /* concerne le ifndef de CAS_D_ECOLE */
Fonctions et structures
Maintenant qu'il existe une structure il est possible de travailler sans utiliser de variable globale ou une multitude d'argument dans la fonction.
Voici le prototype à placer dans casdecole.h suivi de sa définition à écrire dans casdecole.c
Dans le .h : en dessous de /* Prototypes ... */
void opt_main(mes_options *opt);
Ajouter ce qui est, en surbrillance, dans le casdecole.c:
#include <stdio.h>
#include "casdecole.h"
/* Notez que ce opt n'est pas global au code, ce montre qu'on peut utiliser les mêmes noms de variables dans tout le code sans avoir les désavantage d'une variable global, initilisée en dehors du main()... */
void opt_main(mes_options *opt)
{
int count = 1;
printf(“les arguments disponibles sont : \n”);
/* Notez le opt-> utilisé à la place du point lors de l'utilisation de référence, on retrouve cette syntaxe dans le Perl avec a=() ou a={} ... */
for( ; count < opt->argc ; count++)
{
printf(" %4d: %s\n", count, opt->argv[count]);
}
}
int main(int argc, char *argv[], char **env) /* **env équivalent à *env[] */
{
mes_options opt; /* Création d'une instance de "mes_options" */
/* représentée par "opt" */
opt.argv = argv ;
opt.argc = argc;
opt.env = env;
printf("Nombre d’argument disponible lors de l'appel du programme '%s' est égal à '%d'\n", argv[0] , argc);
/* La suite */
/* Utilisation de la référence du pointeur opt par le & */
opt_main(&opt);
return(argc);
}
compiler le tout
Compilation sans utiliser le "linker"
Syntaxe :
- g++ -c -o sourceN.o sourceN.c
- sourceN représente les fichiers sources '.c' à compiler avant de toute fusionner (linker...)
Pour notre mini-projet :
- g++ -c -o casdecole.o casdecole.c
Compilation rassemblant tous les objets compilés
Syntaxe :
- gcc sourceN.h sourceN+1.h -o prg_exec objectN.o
- sourceN.h sont tous les .h du projet
- prg_exec est le fichier qui va être créé à partir de vos sources et qui contiendra tout le code (assembleur) de vos .h et .c
- objectN.o tout ce qui a été créé ci-dessus avec l'option -c
<!> Ce procédé peut paraitre rébarbatif pour 2 fichiers, mais le savoir va faire gagner du temps de compilation lorsqu'il y aura plusieurs source .c et .h ...
Pour finir
- gcc casdecole.h -o casdecole casdecole.o
<!> Ici l'emploi du compilateur C et C++ est volontaire (gcc et g++). Un compilateur C++ sait parfaitement compiler un projet en C pure. L'inverse n'est évidement pas possible pour des raisons historiques.
Structures complexes
Ce qui est appelé "structure complexe" est une structure simple avec soit sa propre structure en définition et/ou soit une déclaration d'un prototype de fonction. C’est-à-dire, non plus y stocker différents types de variable on y ajoute aussi des fonctions pour y être appelées par la structure elle-même.
Voici un exemple concret qui va se greffer au reste du projet :
(à ajouter dans notre .h)
typedef struct
{
char *fonction;
int droits ;
/* déclaration de fonction (prototype) */
void (*function)(struct *mon_menu, char *, char *);
mon_menu *next ;
mon_menu *prev ;
} command_tbl;
typedef mon_menu ma_structure_menu
Définir sa propre structure avant même qu'elle soit définie entièrement est quelque chose de plutôt abstrait pour un nom habitué. Cette possibilité est importante pour un programme devant gérer des listes de données (liste chaînée)…
Personnellement, j'adore utiliser cette forme, car elle me donne l'impression d'avoir la puissance du C++, la gestion d'objet, alors qu'un compilateur C est utilisé...
Définition et attribution
à placer après la définition des prototypes du .H ou dans le .c :
/* Une autre façon de déclarer une structure dite ‘complexe’ */
struct
{
char *nom; /* name of command */
void (*function)(char *, char*, char*); /* pointer to function */
int droits;
} on_msg_commands[] =
/* Attributions...
Commande function droits */
{
{ "SELF", do_menu, 0},
{ "HELP", show_help, 0},
/*
.
:
*/
{ NULL, null(void(*)()), 0 }
};
(à placer dans le .c)
void do_menu(mon_menu opt)
{
/* fonction gérant les droits d'accès et exécution des argv */
}
Arrêt des hostilités
Cette partie, ci-dessus, n'est hélas pas terminée d'être traitée dans ce tuto.
Voici un aperçu de comment s'en servir mais sans explications, désolé :
void show_help(char *from, char *to, char *msg)
{
/* ... */
}
/* ce code doit-être placé dans une fonction ou main() ;) */
/* command, from, to msg sont de stype char * */
for(i = 0; on_msg_commands[i].name != NULL; i++)
/* on vérifie que le nom, mais on pourrait filtrer sur des critères comme userlvl... */
if(STRCASEEQUAL(on_msg_commands[i].nom, command))
{
on_msg_commands[i].function(from, to, *msg?msg:NULL);
}
static et extern
static rend une variable ou fonction accessible uniquement dans le fichier où c'est déclaré.
fichier.c
/* variable non accessible ailleur que dans ce fichier */
static int var_protect = 198;
extern déclare qu'une fonction est déclarée ailleurs que dans le fichier où elle est définie :
/* fonction déclarée ailleurs que dans ce fichier */
extern void la_fonction(void);
Le compilateur fait ainsi l'économie de recherche dans le fichier de déclaration. Seul le linkeur fait cette recherche pour remplacer la référence par la définition.
Le bon "const"
const règle l'accès à une données en lecture seule. Cette donné peut-être un emplacement mémoire, un pointeur ou une référence…
const char *p1 = "mon discours";
char const *p2 = "mon discours 2";
char *p3 const = "mon discours 3";
Ces formes n'ont pas les même signification pour le compilateur…
Histoire / Standard utilisé
Cette doc a été finalisée en 2010 environ, depuis la norme ISO du C a changé. M'obligeant à apporter quelques modifications pour que tout fonctionne avec les outils de 2021.
Or, depuis 1996 où j'employais cette forme, les standards du C on évolués. Aujourd'hui en 2021, les compilateurs actuelles ne fonctionnent plus comme en 96. C'était pourquoi sa version est précisée ici.
gcc 9.4.0 de 2019
Pour en savoir plus sur les standard du C, regarder l'option -std= où se trouve toutes les normes employées. Ceci pour avertir le développeur qu'un standard à l'autre, les projets peuvent ne pas se compiler sans employer le bon standard du moment où vous gérez votre projet... Donc mémorisez bien la norme par défault, ici.
Conclusion
Travailler avec une structure donne l'avantage de tout placer, données et méthodes (fonctions), à l'intérieur du projet. Passer en argument cela évite tout problème lié aux variables globales, d'appelle de fonction... De plus, il devient plus facile de gérer la mémoire et sa libération avec free() quand c'est utile. Avec cette méthode, on peut donc considérer notre structure comme le pointeur this du C++ et l'ajouter dans chaque fonction.