Conseils de programmation

Source: https://www.cs.uky.edu/~raphael/programming.html

Les étudiants des classes CS de niveaux junior et senior doivent connaître certaines informations de base sur C , Unix et les outils de développement logiciel . Cette page détaille une partie de ce contexte et suggère quelques exercices .

C

  1. C vous permet de déclarer des variables en dehors de toute procédure. Ces variables sont appelées variables globales .
  • Une variable globale est allouée une fois au démarrage du programme et reste en mémoire jusqu'à la fin du programme.
  • Une variable globale est visible par toutes les procédures du même fichier.
  • Vous pouvez rendre une variable globale déclarée dans le fichier Ac visible à toutes les procédures de certains autres fichiers Bc, Cc, Dc, ... en la déclarant avec le modificateur extern dans les fichiers Bc, Cc, Dc, ... comme dans cet exemple :
    externe int la variable
    Si vous avez plusieurs fichiers partageant la variable, vous devez la déclarer extern dans un fichier d'en-tête foo.h (décrit ci-dessous ) et utiliser #include foo.h dans les fichiers Bc, Cc, Dc, ... . Vous devez déclarer la variable dans exactement un fichier sans le modificateur extern , sinon elle ne sera jamais allouée du tout.
  • Ce n'est pas une bonne idée d'utiliser trop de variables globales, car vous ne pouvez pas localiser les endroits où elles sont accessibles. Mais il existe des situations où les variables globales permettent d’éviter de transmettre beaucoup de paramètres aux fonctions.
  1. Les chaînes sont des pointeurs vers des tableaux de caractères terminés par un caractère nul.
  • Vous déclarez une chaîne sous la forme char *variableName .
  • Si votre chaîne est constante, vous pouvez simplement lui attribuer une chaîne littérale :
    char *myString = "Ceci est un exemple de chaîne";
  • Une chaîne vide n'a que le terminateur nul :
    maChaîne = ""; // chaîne vide, longueur 0, contenant null

Un pointeur nul n'est pas une valeur de chaîne valide :
maChaîne = NULL ; // chaîne invalide
Vous pouvez utiliser une telle valeur pour indiquer la fin d'un tableau de chaînes :
argv[0] = "NomProg";

argv[1] = "premierParam";

argv[2] = "secondParam";

  • argv[3] = NULL; // terminateur

Si votre chaîne est calculée au moment de l'exécution, vous devez réserver suffisamment d'espace pour la conserver. La pièce doit être suffisante pour contenir le nul à la fin :
char *maChaîne;

maChaîne = (char *) malloc(strlen(uneChaîne) + 1); // alloue de l'espace

  • strcpy(maChaîne, uneChaîne); // copie une Chaîne dans maChaîne
  • Pour éviter les fuites de mémoire, vous devez éventuellement restituer l'espace que vous allouez avec malloc en utilisant free . Son paramètre doit être le début de l'espace renvoyé par malloc :
    free((void *) maChaîne);
    Pour garder votre code propre et lisible, vous devez appeler free() dans la même procédure dans laquelle vous appelez malloc() ; vous pouvez appeler d'autres procédures entre ces deux points pour manipuler votre chaîne.
  • Si vous copiez des chaînes, vous devez faire très attention à ne jamais copier plus d'octets que la structure de données de destination ne peut en contenir. Les débordements de tampon sont la cause la plus courante de failles de sécurité dans les programmes. En particulier, envisagez d'utiliser strncpy() et strncat() au lieu de strcpy() et strcat() .

Si vous utilisez C++, vous devez convertir vos objets chaîne en chaînes de style C avant de les transmettre dans un appel système.
string myString // Cette déclaration ne fonctionne qu'en C++

...

  • someCall(myString.c_str())
    Malheureusement, c_str() renvoie une chaîne immuable. Si vous avez besoin d'une chaîne mutable, vous pouvez soit copier les données en utilisant strcpy() (comme ci-dessus), soit convertir le type :
    someCall(const_cast(myString.c_str()))
    La conversion n'est pas aussi sûre que la copie, car someCall() pourrait en fait modifier la chaîne, ce qui confondrait toute partie du programme qui suppose que myString est constante, ce qui est le comportement habituel des chaînes C++.
  1. Un tampon est une région de mémoire agissant comme un conteneur de données. Même si les données peuvent avoir une interprétation (comme un tableau de structures avec de nombreux champs), les programmes qui lisent et écrivent des tampons les traitent souvent comme des tableaux d'octets. Un tableau d'octets n'est pas la même chose qu'une chaîne, même s'ils sont tous deux déclarés char * ou char [] .
  • Ils ne peuvent pas contenir de caractères ASCII et ne peuvent pas se terminer par un caractère nul.
  • Vous ne pouvez pas utiliser strlen() pour trouver la longueur des données dans un tampon (car le tampon peut contenir des octets nuls). Au lieu de cela, vous devez déterminer la longueur des données en fonction de la valeur de retour de l'appel système (généralement read ) qui a généré les données.
  • Vous ne pouvez pas utiliser strcpy() , strcat() ou des routines associées sur des tampons d'octets ; à la place, vous devez utiliser memcpy() ou bcopy() .

Vous écrivez un tampon de 123 octets dans un fichier en utilisant un code comme celui-ci :
char *fileName = "/tmp/foo"

#define TAILLE BUFS 4096

char buf[BUFSIZE]; // tampon contenant au plus BUFSIZE octets

...

int outFichier ; // descripteur de fichier, un petit entier

int octetsToWrite ; // nombre d'octets restant à écrire

char *outPtr = buf;

...

if ((outFile = create(fileName, 0660)) < 0) [ // échec

// voirles autorisations des fichiers pour comprendre 0660

erreur(nomfichier); // affiche la cause

sortie(1); // et quitte

[

octetsToWrite = 123 ; // initialisation ; 123 n'est qu'un exemple

while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) [

// tous les octets n'ont pas encore été écrits

if (bytesWritten < 0) [ // échec

perror("écrire");

sortie(1);

[

outPtr += octetsÉcrits ;

octetsToWrite -= octetsWritten ;

  • [

Pour que le compilateur alloue de l'espace aux tampons, vous devez déclarer le tampon avec une taille que le compilateur peut calculer, comme dans
#define TAILLE BUFS 1024

  • char buf[BUFSIZE];
    Si vous déclarez simplement le tampon sans taille :
    char buf[];
    alors il a une taille inconnue et C n'alloue aucun espace. C'est acceptable si buf est un paramètre formel (c'est-à-dire qu'il apparaît dans l'en-tête d'une procédure) ; le paramètre réel (fourni par l'appelant) a une taille. Mais ce n'est pas acceptable si buf est une variable. Si vous ne connaissez pas la taille du tampon au moment de la compilation, vous devez utiliser un code comme celui-ci :
    char *buf = (char *) malloc(bufferSize);
    où bufferSize est le résultat d'exécution d'un calcul.
  1. Vous pouvez allouer et libérer de la mémoire de manière dynamique.

Instances individuelles de tout type :
typedef ... monType ;

monType *maVariable = (monType *) malloc(sizeof(myType));

// vous pouvez maintenant accéder à *myVariable.

...

  • free((void *) maVariable);
    Encore une fois, c'est une bonne pratique de programmation d'invoquer free() dans la même routine dans laquelle vous appelez malloc() .

Tableaux unidimensionnels de tout type :
monType *myArray = (myType *) malloc(arrayLength * sizeof(myType));

// myArray[0] .. myArray[arrayLength - 1] sont maintenant alloués.

...

  • free((void *) monArray);

Les tableaux bidimensionnels sont représentés par un tableau de pointeurs, chacun pointant vers un tableau :
monType **myArray = (myType **) malloc(numRows * sizeof(myType *));

int indexligne ;

pour (rowIndex = 0 ; rowIndex < numRows ; rowIndex += 1) [

monArray[rowIndex] = (monType *) malloc(numColumns * sizeof(monType));

[

// monTableau[0][0] .. monTableau[0][numColumns-1] .. monTableau[numRows-1][numColumns-1]

// sont maintenant alloués. Vous souhaiterez peut-être les initialiser.

...

pour (rowIndex = 0 ; rowIndex < numRows ; rowIndex += 1) [

free((void *) monArray[rowIndex]);

[

free((void *) monArray);

  • Si vous utilisez C++, ne mélangez pas new/delete avec malloc/free pour la même structure de données. L'avantage de new/delete pour les instances de classe est qu'elles appellent automatiquement des constructeurs, qui peuvent initialiser les données, et des destructeurs, qui peuvent finaliser les données. Lorsque vous utilisez malloc/free , vous devez explicitement initialiser et finaliser.
  1. Entiers
  • C représente généralement des entiers sur 4 octets. Par exemple, le nombre 254235 est représenté par le nombre binaire 00000000,00000011,11100001,00011011.
  • D'un autre côté, le texte ASCII représente les nombres comme n'importe quel autre caractère, avec un octet par chiffre en utilisant un codage standard. En ASCII, le nombre 254235 est représenté par 00110010, 00110101, 00110110, 00110010, 00110011, 00110101.
  • Si vous devez écrire un fichier d'entiers, il est généralement plus efficace en termes d'espace et de temps d'écrire les versions à 4 octets que de les convertir en chaînes ASCII et de les écrire. Voici comment écrire un seul entier dans un fichier ouvert :
    write(outFile, &myInteger, sizeof(myInteger))

Vous pouvez examiner les octets individuels d'un entier en le transformant en une structure de quatre octets :
int Adresse IP ; // stocké sous forme d'entier, compris comme 4 octets

structure typedef [

char octet1, octet2, octet3, octet4;

[ IPDetails_t;

IPDetails_t *détails = (IPDetails_t *) (&IPAddress);

printf("l'octet 1 est %o, l'octet 2 est %o, l'octet 3 est %o, l'octet 4 est %o\n",

  • détails->octet1, détails->octet2, détails->octet3, détails->octet4);
  • Les entiers multi-octets peuvent être représentés différemment sur différentes machines. Certains (comme Sun SparcStation) mettent l'octet de poids fort en premier ; d'autres (comme l'Intel i80x86 et ses descendants) mettent l'octet de poids faible en premier. Si vous écrivez des données entières qui pourraient être lues sur d'autres machines, convertissez les données dans l'ordre des octets "réseau" par htons() ou htonl() . Si vous lisez des données entières qui auraient pu être écrites sur d'autres machines, convertissez les données de l'ordre "réseau" en votre ordre d'octets local par ntohs() ou ntohl() .

Vous pouvez prédire la disposition de la mémoire des structures et la valeur que sizeof() renverra. Par exemple,
structure foo [

char a; // utilise 1 octet

// C insère ici un pad de 3 octets pour que b puisse commencer sur une limite de 4 octets

int b; // utilise 4 octets

court c non signé; // utilise 2 octets

caractère non signé d[2] ; // utilise 2 octets

  1. [ ;
    Par conséquent, sizeof(struct foo ) renvoie 12. Cette prévisibilité (pour une architecture donnée) est la raison pour laquelle certains appellent C un « langage assembleur portable ». Vous devez prédire la disposition de la structure lors de la génération de données qui doivent suivre un format spécifique, tel qu'un en-tête sur un paquet réseau.
  2. Vous pouvez déclarer des pointeurs en C vers n'importe quel type et leur attribuer des valeurs qui pointent vers des objets de ce type.

En particulier, C permet de construire des pointeurs vers des entiers :
int unEntier;

int *intPtr = &someInteger; // déclare une variable à valeur de pointeur et attribue une valeur de pointeur appropriée

unAppel(intPtr); // passe un pointeur comme paramètre réel

  • unAppel(&unInteger); // a le même effet que ci-dessus
  • La procédure de la bibliothèque AC qui prend un pointeur vers une valeur modifie très probablement cette valeur (elle devient un paramètre "out" ou "in out"). Dans l'exemple ci-dessus, il est très probable que someCall modifie la valeur de l'entier someInteger .

Vous pouvez créer un pointeur vers un tableau d’entiers et l’utiliser pour parcourir ce tableau.
#définir ARRAY_LENGTH 100

int intArray[ARRAY_LENGTH];

int *intArrayPtr;

...

somme entière = 0 ;

pour (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) [

somme += *intArrayPtr;

  • [

Vous pouvez créer un pointeur vers un tableau de structures et l'utiliser pour parcourir ce tableau.
#définir ARRAY_LENGTH 100

typedef struct [int foo, bar;[ paire_t; // pair_t est un nouveau type

paire_t structArray[ARRAY_LENGTH]; // structArray est un tableau d'éléments ARRAY_LENGTH pair_t

paire_t *structArrayPtr; // structArrayPtr pointe vers un élément pair_t

...

somme entière = 0 ;

pour (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) [

somme += structArrayPtr->foo + structArrayPtr->bar;

  • [
  • Lorsque vous ajoutez un entier à un pointeur, le pointeur avance d’autant d’éléments, quelle que soit la taille des éléments. Le compilateur connaît la taille et fait ce qu'il faut.
  1. Sortir
  • Vous formatez la sortie avec printf ou sa variante, fprintf .
  • La chaîne de format utilise %d , %s , %f pour indiquer qu'un entier, une chaîne ou un réel doit être placé dans la sortie.
  • La chaîne de format utilise \t et \n pour indiquer une tabulation et une nouvelle ligne.
  • Exemple:
    printf("Je pense que le nombre %d est %s\n", 13, "chanceux");
  • Le mélange de printf() , fprintf() et cout peut ne pas imprimer les éléments dans l'ordre souhaité. Ils utilisent des zones de transit indépendantes (« tampons ») qu'ils impriment lorsqu'elles sont pleines.
  1. La routine main() prend des paramètres de fonction qui représentent les paramètres de ligne de commande .
  • Une façon courante d'écrire la routine principale est la suivante :
    int main(int argc; char *argv[]);
    Ici, argc est le nombre de paramètres et argv est un tableau de chaînes, c'est-à-dire un tableau de pointeurs vers des tableaux de caractères terminés par un caractère nul.

Par convention, le premier élément de argv est le nom du programme lui-même.
int main(int argc; char *argv[]);

printf("J'ai %d paramètres ; mon nom est %s et mon premier paramètre est %s\n",

  • argc, argv[0], argv[1]);
  1. Fonctionnalités linguistiques pratiques
  • Vous pouvez incrémenter un entier ou faire pointer un pointeur vers l'objet suivant en utilisant l' opérateur ++ . Il est généralement préférable de placer cet opérateur après la variable : myInt++ . Si vous placez le ++ avant la variable, alors la variable est incrémentée avant d'être évaluée, ce qui est rarement ce que vous souhaitez.

Vous pouvez créer une affectation dans laquelle la variable de gauche participe en tant que première partie de l'expression du côté droit :
monInt -= 3 ; // équivalent à monInt = monInt - 3

monInt *= 42 ; // équivalent à monInt = monInt * 42

  • monInt += 1 ; // équivalent et peut-être préférable à myInt++
  • Vous pouvez exprimer les nombres en décimal, octal (en préfixant le chiffre 0 , comme dans 0453 ) ou en hexadécimal (en préfixant 0x , comme dans 0xffaa ).

Vous pouvez traiter un entier comme un ensemble de bits et effectuer des opérations au niveau du bit :
monInt = monInt | 0444 ; // OU au niveau du bit ; 0444 est en octal

monInt &= 0444 ; // ET au niveau du bit avec un raccourci d'affectation

  • myInt = quelque chose ^ peu importe ; // XOR au niveau du bit

C et C++ ont des expressions conditionnelles. Au lieu d'écrire
si (a < 7)

a = une valeur

autre

  • a = uneAutreValeur ;
    tu peux écrire
    une = une < 7 ? uneValeur : uneAutreValue;
  • Les affectations renvoient la valeur du côté gauche, vous pouvez donc inclure une affectation dans des expressions plus grandes telles que des conditions. Mais vous devez suivre la convention selon laquelle de telles affectations sont toujours entourées de parenthèses pour indiquer à la fois à quelqu'un qui lit votre code et au compilateur que vous parlez réellement d'une affectation et non d'un test d'égalité. Par exemple, écrivez
    si ((s = socket(...)) == -1)
    pas
    si (s = socket(...) == -1)
    La deuxième version est à la fois plus difficile à lire et, dans ce cas, incorrecte, car l'opérateur d'égalité == a une priorité plus élevée que l'opérateur d'affectation = .
  1. Les programmes qui ne sont pas trivialement courts doivent généralement être décomposés en plusieurs fichiers sources , chacun avec un nom se terminant par .c (pour les programmes C) ou .cpp (pour les programmes C++).
  • Essayez de regrouper les fonctions qui manipulent les mêmes structures de données ou ont des objectifs connexes dans le même fichier.
  • Tous les types, fonctions, variables globales et constantes manifestes nécessaires à plusieurs fichiers source doivent également être déclarés dans un fichier d'en-tête , avec un nom se terminant par .h .
  • À l'exception des fonctions en ligne, ne déclarez pas de corps de fonction (ou tout ce qui amène le compilateur à générer du code ou à allouer de l'espace) dans le fichier d'en-tête.
  • Chaque fichier source doit faire référence aux fichiers d'en-tête dont il a besoin avec une ligne #include .
  • N'incluez jamais un fichier .c .
  1. Lorsque vous disposez de plusieurs fichiers sources, vous devez relier tous les fichiers objets compilés ainsi que toutes les bibliothèques dont votre programme a besoin.
  • La méthode la plus simple consiste à utiliser le compilateur C, qui connaît les bibliothèques C :
    gcc *.o -o monProgramme
    Cette commande demande au compilateur de lier tous les fichiers objets à la bibliothèque C (qui est implicitement incluse) et de placer le résultat dans le fichier myProgram , qui devient exécutable.
  • Si votre programme a besoin d'autres bibliothèques, vous devez les spécifier après vos fichiers objets, car l'éditeur de liens ne collecte que les routines des bibliothèques dont il sait déjà qu'il a besoin, et il lie les fichiers dans l'ordre que vous spécifiez. Donc, si vous avez besoin d'une bibliothèque telle que libxml2 , votre commande de liaison devrait ressembler à ceci :
    gcc *.o -lxml2 -o monProgramme
    Le compilateur sait comment rechercher dans différents répertoires standard la version actuelle de libxml2 .
  1. Débogage des programmes C
  • Si vous obtenez une erreur de segmentation, vous avez probablement un index hors plage, un pointeur non initialisé ou un pointeur nul.
  • Vous pouvez insérer des instructions d'impression dans votre programme pour vous aider à localiser une erreur.
  • Le débogage aura probablement plus de succès si vous utilisez gdb (décrit ci-dessous ) pour déterminer où se trouve votre erreur.
  • Les programmes qui s'exécutent pendant une longue période doivent veiller à libérer toute la mémoire qu'ils allouent, sinon ils finiront par manquer de mémoire. Pour déboguer les fuites de mémoire, vous pouvez consulter ces articles sur le débogage des fuites de mémoire C et des fuites de mémoire C++ .

Unix

fichiers standard , commandes , appels système , autorisations de fichiers

  1. Par convention, chaque processus commence avec trois fichiers standards ouverts : entrée standard, sortie standard et erreur standard, associés aux descripteurs de fichiers 0, 1 et 2.
  • L'entrée standard est généralement connectée à votre clavier. Tout ce que vous tapez va au programme.
  • La sortie standard est généralement connectée à votre écran. Quels que soient les résultats du programme, ils deviennent visibles.
  • L’erreur standard est également généralement liée à votre écran.
  • Vous pouvez utiliser le shell pour appeler des programmes afin que la sortie standard d'un programme soit directement liée (« redirigée ») à l'entrée standard d'un autre programme :
    ls | toilettes

Vous pouvez utiliser le shell pour appeler des programmes afin que l'entrée et/ou la sortie standard soit liée à un fichier :
ls > lsOutFichier

wc < lsOutFile

  • sort -u < largeFile > sortedFile
  • En général, les programmes ne savent pas ou ne se soucient pas si le shell a réorganisé la signification de leurs fichiers standard.
  1. Commandes Unix
  • Les commandes ne sont que les noms de fichiers exécutables. La variable d'environnement PATH indique au shell où les rechercher. Généralement, cette variable a une valeur telle que /bin:/usr/bin:/usr/local/bin:. .
  • Pour voir où le shell trouve un programme particulier, par exemple vim , dites où vim .
  1. Les appels système et les appels de bibliothèque suivent certaines conventions importantes.
  • La valeur de retour de l'appel indique généralement si l'appel a réussi (généralement la valeur est 0 ou positive) ou a échoué (généralement la valeur est -1).

Vérifiez toujours la valeur de retour des appels à la bibliothèque. Lorsqu'un appel système échoue, la fonction perror() peut afficher l'erreur (en erreur standard) :
int fd;

char *filename = "monfichier";

if ((fd = open(nom de fichier, O_RDONLY)) < 0) [

perror(nom de fichier); // pourrait afficher "monfichier : aucun fichier ou répertoire de ce type"

  • [
  • Une page de manuel pour un appel système ou une routine de bibliothèque peut répertorier un type de données qu'elle ne définit pas, tel que size_t ou time_t ou O_RDONLY . Ces types sont généralement définis dans les fichiers d'en-tête mentionnés dans la page de manuel ; vous devez inclure tous ces fichiers d'en-tête dans votre programme C.
  1. Les autorisations de fichiers sous Unix sont généralement exprimées par des nombres octaux.
  • Dans l'exemple de creat() ci-dessus, 0660 est un nombre octal (c'est ce que signifie le 0 initial), représentant 110 110 000 binaires. Ce nombre octal accorde des autorisations de lecture et d'écriture, mais pas d'exécution, au propriétaire du fichier et au groupe du fichier, mais aucune autorisation aux autres utilisateurs.
  • Vous définissez les autorisations lorsque vous créez un fichier par le paramètre de l' appel creat() .
  • La commande ls -l vous montre les autorisations des fichiers.
  • Vous pouvez modifier les autorisations d'un fichier que vous possédez en utilisant le programme chmod .
  • Tous vos processus ont une caractéristique appelée umask, généralement représentée par un nombre octal. Lorsqu'un processus crée un fichier, les bits de l'umask sont supprimés des autorisations spécifiées dans l' appel creat() . Donc, si votre umask est 066, alors les autres ne peuvent pas lire ou écrire les fichiers que vous créez, car 066 représente les autorisations de lecture et d'écriture pour votre groupe et pour d'autres personnes. Vous pouvez inspecter et modifier votre umask à l'aide du programme umask , que vous invoquez généralement dans votre script de démarrage shell (en fonction de votre shell, ~/.login ou ~/.profile ).

Outils de développement de logiciels

éditeur de texte , débogueur , compilateur , pages de manuel , make , recherche ,

  1. Utilisez un éditeur de texte pour créer, modifier et inspecter votre programme. Il existe plusieurs éditeurs de texte raisonnables.
  • L' éditeur vim et son interface graphique, gvim , demandent un certain effort d'apprentissage, mais ils fournissent un ensemble d'outils de très haute qualité pour éditer les fichiers de programme, notamment la coloration syntaxique, la correspondance des parenthèses, la complétion des mots, l'indentation automatique, la recherche par balise (qui déplace vous déplacez rapidement d'un endroit où le programme appelle une fonction à l'endroit où la fonction est définie) et une recherche intégrée dans les pages de manuel. Vim est conçu pour une utilisation au clavier ; vous n'avez jamais besoin d'utiliser la souris si vous ne le souhaitez pas. Il est disponible gratuitement pour les systèmes d'exploitation Unix, Win32 et Microsoft. Il s'agit de la version la plus développée de la série d'éditeurs qui comprend ed , ex , vi etElvis . Vous pouvez lire la documentation en ligne de vim et obtenir une assistance immédiate via la commande :help de vim .
  • L' éditeur emacs est, au contraire, plus chargé de fonctionnalités que vim . Cela demande également des efforts importants pour apprendre. Il est également disponible gratuitement pour les systèmes d'exploitation Unix et Microsoft. Vous pouvez trouver de la documentation ici .
  • Il existe de nombreux autres éditeurs de texte disponibles, mais en général, ils ne vous offrent pas les deux fonctionnalités les plus utiles dont vous avez besoin pour créer des programmes : l'indentation automatique et la coloration syntaxique. Cependant, ces éditeurs de texte ont souvent l’avantage d’être plus faciles à prendre en main, compte tenu de leurs capacités limitées. Parmi ces éditeurs de texte de qualité inférieure figurent (pour Unix) pico , gedit et joe et (pour Microsoft) notepad et word .
  • Vous connaissez peut-être un environnement de développement intégré (IDE) tel qu'Eclipse, Code Warrior ou .NET. Ces environnements disposent généralement d'éditeurs de texte intégrés aux débogueurs et aux compilateurs. Si vous utilisez un tel IDE, il est logique d'utiliser les éditeurs de texte associés.
  1. gdb est un débogueur qui comprend vos variables et la structure de votre programme.
  • Vous pouvez trouver de la documentation ici .
  • Pour utiliser gdb efficacement, vous devez transmettre l' indicateur -g au compilateur C ou C++.
  • Si votre programme myProgram a échoué en laissant un fichier appelé core , essayez gdb myProgram core .
  • Vous pouvez également exécuter votre programme depuis le début sous le contrôle de gdb : gdb myProgram .
  • Toutes les commandes de gdb peuvent être abrégées en un préfixe unique.
  • La commande help est très utile.
  • La commande Where affiche la pile d'appels, y compris les numéros de ligne indiquant où se trouve chaque routine. C'est la première commande que vous devriez essayer lorsque vous déboguez un fichier core.
  • Pour imprimer la valeur d'une expression (vous pouvez inclure vos variables et les opérateurs C habituels), tapez print expression , comme dans
    imprimer (myInt + 59) & 0444;
  • Pour voir votre programme, essayez list myFunction ou list myFile.c:38 .
  • Pour définir un enregistrement d'activation différent comme actuel, utilisez la commande up (pour plus récent) ou down (pour moins récent).
  • Vous pouvez définir un point d'arrêt sur n'importe quelle ligne de n'importe quel fichier. Par exemple, vous pouvez dire break foo.p:38 pour définir un point d'arrêt à la ligne 38 du fichier foo.p . Chaque fois que votre programme atteint cette ligne pendant son exécution, il s'arrêtera et gdb vous demandera des commandes. Vous pouvez examiner des variables, par exemple, ou avancer dans le programme.
  • La commande suivante avance d'une instruction (appelant et revenant de toute procédure si nécessaire).
  • La commande step avance d'une instruction, mais si l'instruction implique un appel de procédure, elle entre dans la procédure et s'arrête à la première instruction.
  • Si vous entrez la commande set follow-fork-mode child , alors lorsque votre programme exécute l' appel fork() , gdb continuera à déboguer l'enfant, pas le parent.
  • Quittez gdb en entrant la commande quit .
  • Vous préférerez peut-être utiliser l' interface graphique ddd pour gdb .
  1. Donnez toujours aux programmes de compilation gcc ou g++ l' indicateur -Wall pour activer un niveau élevé d'avertissements. De même, donnez à javac le drapeau -Xlint:all . Ne remettez pas un programme qui génère des avertissements au moment de la compilation.
  2. Vous pouvez lire le manuel pour obtenir des détails sur les programmes, les routines de la bibliothèque C et les appels système Unix en utilisant le programme man , comme dans man printf ou man gcc .
  • Parfois, la fonction souhaitée se trouve dans une section spécifique du manuel Unix et vous devez la demander explicitement : man 2 open ou man 3 printf . La section 1 couvre les programmes, la section 2 couvre les appels système, la section 3 couvre la bibliothèque C et la section 8 couvre l'administration du système. Vous n’avez probablement pas besoin des autres sections.
  • Vous pouvez déterminer si un programme, une routine de bibliothèque C ou un appel système Unix est pertinent pour un sujet en utilisant l' indicateur -k , comme dans man -k print .
  1. Utilisez le programme make pour organiser des recettes pour recompiler et relier votre programme lorsque vous modifiez un fichier source.

Si votre programme est composé de plusieurs fichiers, vous pouvez les compiler séparément puis les relier entre eux. Vous compilez avec l'indicateur -c et utilisez l' indicateur -o pour indiquer le fichier de sortie. Un makefile raisonnable pourrait ressembler à ceci :
SOURCES = pilote.c entrée.c sortie.c

OBJETS = pilote.o entrée.o sortie.o

EN-TÊTES = commun.h

CFLAGS = -g -Mur

programme : $(OBJETS)

$(CC) $(CFLAGS) $(OBJECTS) -o programme

$(OBJETS) : $(EN-TÊTES)

testRun : programme

  • programme < testData
    Ce makefile utilise une définition intégrée de CC et une règle intégrée pour convertir les fichiers source C comme driver.c en leur fichier objet. Si vous modifiez uniquement input.c , alors make testRun entraînera la reconstruction du compilateur input.o , puis obligera le compilateur à relier les objets, créant program , puis à exécuter le programme avec l'entrée standard redirigée depuis le fichier testData .
  • Si vous disposez de nombreux fichiers source et de nombreux fichiers d'en-tête, vous souhaiterez peut-être utiliser le programme makedepend pour créer automatiquement les règles Makefile qui spécifient comment les fichiers source dépendent des fichiers d'en-tête. L'exemple ci-dessus suppose que tous les fichiers sources dépendent de tous les fichiers d'en-tête, ce qui n'est souvent pas le cas.
  1. Le programme grep peut rechercher rapidement une définition ou une variable, notamment dans les fichiers include :
    grep "struct timeval [" /usr/include/*/*.h

Des exercices

Faites ces exercices en C.

  1. Écrivez un programme appelé atoi qui ouvre un fichier de données nommé sur la ligne de commande et y lit une seule ligne d'entrée, qui doit contenir un entier représenté en caractères ASCII. Le programme convertit cette chaîne en entier, multiplie l'entier par 3 et imprime le résultat sur la sortie standard. Le programme ne doit pas utiliser la fonction atoi() . Vous devez utiliser le programme make . Votre Makefile doit avoir trois règles : atoi , run (qui exécute votre programme sur vos données de test standard et redirige la sortie vers un nouveau fichier) et clean(qui supprime les fichiers temporaires). Assurez-vous que votre programme s'exécute correctement avec des données incorrectes et se termine avec un message utile si le fichier de données est manquant ou illisible. Parcourez votre programme en le démarrant avec gdb , en plaçant un point d'arrêt sur main() et en utilisant la commande step à plusieurs reprises.
  2. Recherchez la page de manuel du programme cat . Codez votre propre version de cat . Votre version doit accepter plusieurs (ou aucun) paramètres de nom de fichier. Il n’est pas nécessaire qu’il accepte des paramètres d’option.
  3. Écrivez un programme removeSuffix qui prend un seul paramètre : le nom d'un fichier de suffixe. Le fichier de suffixe comporte une ligne par entrée. Une entrée est une chaîne non vide, que nous appelons un suffixe , suivie du signe >, suivi d'une autre chaîne (éventuellement vide), que nous appelons un remplacement . Votre programme doit stocker tous les suffixes et leurs remplacements dans une table de hachage. Utilisez un chaînage externe. Votre programme devrait alors lire l'entrée standard. Pour chaque mot délimité par des espaces w dans l'entrée, recherchez le suffixe s le plus long qui apparaît dans w et modifiez w en supprimant s et en insérant les 'remplacement, créant w' . Affiche une ligne par mot modifié, sous la forme w>w' . Ne produisez aucun mot qui n’est pas modifié.