Quelques détails sur la fonction principale. Arguments de la fonction MAIN()

La forme de base d’écriture d’une fonction est la suivante

type_de retour nom_fonction(liste_paramètres) { fonction_corps) Le type de données renvoyées par la fonction est spécifié à l'aide de l'élément type_retour . Sous l'élément liste_paramètres implique une liste de variables séparées par des virgules qui peuvent prendre tous les arguments passés par la fonction.

En C89, si le type de données renvoyé par une fonction n'est pas explicitement spécifié, alors le type int est supposé. En C++ et C99, le type int par défaut n'est pas pris en charge, bien que la plupart des compilateurs C++ fassent toujours cette hypothèse.

Prototypes de fonctions

Dans le langage C++, toutes les fonctions doivent avoir des prototypes, et dans le langage C, les prototypes sont formellement facultatifs, mais hautement souhaitables. La forme générale d’une définition de prototype est la suivante.

type_de retour nom_fonction(liste_paramètres); Par exemple. flotter fn(flotter x); //ou float fn(float);

En C, le mot clé void est utilisé à la place d'une liste de paramètres pour spécifier le prototype d'une fonction qui n'a pas de paramètres. En C++, une liste de paramètres vide dans un prototype de fonction signifie que la fonction n'a aucun paramètre. Le mot void est facultatif dans ce cas.

Renvoyer des valeurs (instruction return)

Le renvoi des valeurs dans les fonctions se fait à l'aide de l'instruction return. il a deux formes de notation.

Retour; retour signification;

En C99 et C++, une forme d'instruction return qui ne spécifie pas de valeur de retour ne doit être utilisée que dans les fonctions void.

Surcharge de fonctions

En C++, les fonctions peuvent surcharge. Lorsqu'une fonction est dite surchargée, cela signifie que deux ou plusieurs fonctions portent le même nom, mais que chaque version de la fonction surchargée a un nombre ou un type de paramètres différent. Prenons un exemple des trois fonctions surchargées suivantes.

Fonction vide (int a)( cout

Dans chaque cas, le type et le nombre d'arguments sont analysés pour déterminer quelle version de func() sera appelée.

Passer des arguments de fonction par défaut

En C++, un paramètre de fonction peut se voir attribuer une valeur par défaut, qui sera automatiquement utilisée si l'argument correspondant n'est pas spécifié lors de l'appel de la fonction. Par exemple.

Void func (int a = 0, int b = 10)() //appel func(); fonction(-1); fonction(-1, 99);

Portée et durée de vie des variables.

Les langages C et C++ définissent des règles de visibilité qui établissent des concepts tels que la portée et la durée de vie des objets. Il existe une portée mondiale et une portée locale.

Mondial une portée existe en dehors de toutes les autres portées. Un nom déclaré dans la portée globale est connu de l'ensemble du programme. Par exemple, une variable globale peut être utilisée par toutes les fonctions du programme. Les variables globales existent tout au long du cycle de vie d'un programme.

Locale la portée est déterminée par les limites du bloc. Un nom déclaré dans une portée locale n'est connu que dans cette portée. Les variables locales sont créées à l'entrée d'un bloc et détruites à la sortie. Cela signifie que les variables locales ne stockent pas leurs valeurs entre les appels de fonction. Pour conserver les valeurs des variables entre les appels, vous pouvez utiliser le modificateur static.

Récursion

En C et C++, les fonctions peuvent s'appeler elles-mêmes. Ce processus est appelé récursivité, et une fonction qui s'appelle elle-même est récursive. A titre d'exemple, utilisons la fonction fact(), qui calcule la factorielle d'un entier.

Int fait (int n) ( int ans; if (n == 1) return 1; ans = fact (n-1) * n; return ans; )

fonction principale()

L'exécution d'un programme C/C++ commence par l'exécution de la fonction main(). (Les programmes Windows appellent la fonction WinMain()) ;

La fonction main() n'a pas de prototype. Par conséquent, diverses formes de la fonction main() peuvent être utilisées. Les variantes suivantes de la fonction main() sont valables pour C et C++.

Int principal(); int principal (int argc, char *argv)

Comme le montre la deuxième forme d’enregistrement, f. main() accepte au moins deux paramètres. Ils sont appelés argc et argv. Ces arguments stockeront respectivement le nombre d’arguments de ligne de commande et un pointeur vers eux. argc est un type entier et sa valeur sera toujours au moins 1 car, comme en C et C++, le premier argument est toujours le nom du programme. Le paramètre argv doit être déclaré comme un tableau de pointeurs de caractères, chaque élément pointant vers un argument de ligne de commande. Vous trouverez ci-dessous un exemple de programme qui démontre l'utilisation de ces arguments.

#inclure en utilisant l'espace de noms std ; int principal (int argc, char *argv) (if (argc

Passer des pointeurs

Même si les langages C et C++ utilisent le passage de paramètres par valeur par défaut, vous pouvez construire manuellement un appel à f. avec passage de paramètres par référence. Pour ce faire, vous devez passer un pointeur vers l'argument. Puisque dans ce cas la fonction reçoit l'adresse de l'argument, Il s'avère qu'il est possible de changer la valeur de l'argument en dehors de la fonction. Par exemple.

Void swap (int *x, int *y) ( int temp; temp = *x; ​​​​​​*x = *y; *y = temp; ) //appel swap (&a, &b);

En C++, l’adresse d’une variable peut être transmise automatiquement à une fonction. Ceci est mis en œuvre en utilisant paramètre de référence. Lors de l'utilisation d'un paramètre de référence, l'adresse de l'argument est transmise à la fonction et la fonction opère sur l'argument plutôt que sur une copie. Pour créer un paramètre de lien, vous devez faire précéder son nom d'une esperluette (&). À l'intérieur f. ce paramètre peut être utilisé normalement, sans utiliser l'opérateur astérisque (*) par exemple.

Échange nul (int &x, int &y)( int temp; temp = x; x = y; y = temp; ) //appel swap (a, b);

Spécificateurs de fonction

C++ définit trois spécificateurs de fonction :

  • en ligne
  • virtuel
  • explicite

Le spécificateur en ligne est une exigence du compilateur : au lieu de créer un appel de fonction, développez son code directement sur la ligne. Si le compilateur ne peut pas insérer une fonction dans une chaîne, il a le droit d'ignorer cette exigence. Le spécificateur en ligne peut définir à la fois des fonctions membres et des fonctions non membres.

En tant que fonction virtuelle (en utilisant le spécificateur virtuel) f. est défini dans la classe de base et remplacé dans la classe dérivée. En utilisant les fonctions virtuelles comme exemple, vous pouvez comprendre comment le langage C++ prend en charge le polymorphisme.

Le spécificateur explicite ne s'applique qu'aux constructeurs. Un constructeur défini comme explicite ne sera utilisé que si l'initialisation correspond exactement à ce qui est spécifié par le constructeur. Aucune conversion ne sera effectuée (c'est-à-dire que le spécificateur explicite crée un "constructeur non-convertissant").

Modèles de fonctions

La forme générale de définition d’une fonction de modèle est la suivante.

Type de modèle> type_de retour nom_fonction (liste_paramètres) ( //corps de la fonction) Ici taper signifie une étiquette d'espace réservé pour le type de données que cette fonction traitera réellement. Dans l'instruction de modèle, vous pouvez définir plusieurs paramètres de type de données sous la forme d'une liste d'éléments séparés par des virgules.

Regardons un exemple.

Modèle void swap (X &a, X &b) ( X temp; temp = a; a = b; b = temp; ) //appel int a, b; flotter x, y ; échanger(a, b); échanger(x, y);

Pointeurs de fonction

Vous pouvez créer un pointeur vers une fonction, comme vers n’importe quel autre objet en langage C. La syntaxe est la suivante.

type_retour (*nom_index)(variable_description); Cette déclaration crée un pointeur vers une fonction appelée nom_index , qui contient des variables variable_description et qui renvoie une valeur de type type_retour .

Une fonction peut être appelée à l’aide d’un pointeur comme suit.

nom_index = nom_fonction; variable = nom_index (variables); Ici, la première ligne crée une référence de fonction nom_fonction. La deuxième ligne appelle en fait la fonction via le pointeur nom_index , auquel les variables sont transmises variables, la valeur est renvoyée à une variable variable .

L'exemple suivant montre la création d'un pointeur et deux façons d'appeler une fonction via un pointeur, ainsi que de transmettre la fonction en tant que paramètre à d'autres fonctions.

Double y ; double (*p)(double x); p = péché ; //création d'un pointeur vers la fonction sin() y = (*p)(2.5); //appel y = p(2.5); //appel //passage de la fonction en paramètre double y; double f(double (*c)(double x), double y)( return c(y); //appelle la fonction passée au pointeur c et renvoie la valeur ) y = f(sin, 2.5); //passe la fonction sin à la fonction f, ainsi que le paramètre qui sera traité

Vous pouvez également créer des tableaux de pointeurs de fonctions.

Int f1(vide); int f2(vide); int f3(vide); int (*p)(void) = (f1, f2, f3) y = (*p)(); //appel de fonction f2 y = p(); //appel de fonction f3

Les possibilités de la famille des langages C sont vraiment illimitées, cependant, dans cette liberté il y a aussi des inconvénients : le programmeur doit toujours garder l'œil ouvert et contrôler le « débordement de tampon » afin que plus tard le programme ne plante pas dans un « débordement de tampon » écran bleu »sur de nombreuses versions différentes de Windows et du matériel pour les utilisateurs. Les mêmes crackers et inverseurs recherchent spécifiquement des vulnérabilités dans le code des programmes C où n'importe quel code de virus peut être implanté ; l'auteur en a parlé plus en détail dans son cours vidéo ; J'y ai beaucoup appris et maintenant mon code est beaucoup plus sécurisé.

Fonction principale.

Chaque programme C et C++ doit avoir une fonction principale ;<значение>et c'est à vous de décider où vous le placez. Certains programmeurs le placent au début du fichier, d'autres à la fin. Cependant, quelle que soit sa position, vous devez vous rappeler les points suivants : Arguments de la fonction « principale ». La procédure de démarrage de Borland C++ envoie trois paramètres (arguments) à la fonction principale : argc, argv et env. - argc, un entier, est le nombre d'arguments de ligne de commande envoyés à la fonction principale, - argv est un tableau de pointeurs vers des chaînes (char *).<=argc; i++) printf(" argv[%d]: %s\n",i,argv[i]); printf("Среда содержит следующие строки:\n"); for (i=0; env[i] != NULL; i++) printf(" env[%d]: %s\n",i,env[i]); return 0; } Предположим, что вы запускаете программу ARGS.EXE со следующей командной строкой: C:> args first_arg "argument avec des blancs" 3 4 "avant-dernier" stop ! Commentaire. Si vous souhaitez que le traitement des caractères d'échappement se produise toujours, c'est-à-dire Pour que WILDARGS.OBJ soit automatiquement lié par l'éditeur de liens, vous devez modifier votre bibliothèque standard C?.LIB pour inclure le fichier WILDARGS.OBJ. Pour ce faire, supprimez SETARGV de la bibliothèque et ajoutez WILDARGS. Cela peut être fait avec les commandes suivantes (nous supposons que les bibliothèques standards et WILDARGS.OBJ sont contenues dans le répertoire courant) : TLIB est décrit dans le chapitre 7, Utilitaires, du Guide de l'utilisateur tlib cs -setargv +wildargs tlib cc -. setargv +wildargs tlib cm -setargv +wildargs tlib cl -setargv +wildargs tlib ch -setargv +wildargs Compilation à l'aide du commutateur -p (convention d'appel de Pascal) Si vous compilez votre programme en utilisant la convention d'appel de Pascal (décrite en détail dans Convention d'appel de Pascal ). Chapitre 9 "Interfaçage avec le langage assembleur", "Guide du programmeur"), vous devez vous rappeler que la fonction principale doit être explicitement déclarée comme une fonction C. Cela peut être fait en utilisant le mot-clé cdecl quelque chose comme ceci : cdecl main(int argc. , char *argv, char *env) La valeur renvoyée par la fonction principale.

Sous DOS 3.x et versions ultérieures, argv est défini comme le chemin complet du programme à lancer.

Lors de l'exécution sous des versions antérieures de DOS, argv pointe vers une chaîne nulle ("").

argv pointe vers la première ligne de commande après le nom du programme. argv pointe vers la deuxième ligne de commande après le nom du programme. argv pointe vers le dernier argument envoyé à main. argv contient NULL. Autrement dit, le début de la fonction principale est une parenthèse ouverte et la fin de la fonction principale est une parenthèse fermante. Une double barre oblique indique le début d'un commentaire. Les commentaires sont ignorés par le compilateur et servent à clarifier les informations contenues dans le code.

Chaque programme écrit en C++ a une fonction principal(), à partir duquel le programme démarre. Fonction principal(), en règle générale, renvoie le résultat de son exécution, qui est signalé par int(entier - entier), qui est écrit avant la fonction principal(). Une fois terminé correctement et avec succès, la fonction principal() renvoie comme résultat 0 . Une valeur de résultat différente de zéro signale une fin anormale du programme.

La valeur renvoyée par le programme une fois terminé peut être utilisée dans le système d'exploitation à des fins de service.

Un exemple typique du premier programme dans n'importe quel langage de programmation est la sortie de texte "Bonjour le monde!":

#inclure int main() ( std::cout<< "Hello, World!\n"; }

Mais tout est-il si simple dans ce programme ? En général, ce petit programme contient à lui seul une très grande couche d’informations qu’il faut comprendre pour développer en C++.

  1. Directif #inclure
    La procédure de démarrage de Borland C++ envoie trois paramètres (arguments) à la fonction principale : argc, argv et env.
    indique au compilateur qu'il est nécessaire d'inclure un certain fichier d'en-tête, dont les composants sont prévus pour être utilisés dans le fichier où la fonction est déclarée principal() . iostream est la bibliothèque d'E/S standard de la STL. Autrement dit, les fonctionnalités des bibliothèques sont déjà utilisées ici, bien qu'elles constituent un standard pour le langage. Et le dernier point concerne les crochets angulaires dans lesquels se trouve le nom de la bibliothèque, qui indiquent qu'il s'agit de l'inclusion de fichiers externes dans le projet, et non de ceux qui font partie du projet. Les mêmes fichiers qui font partie du projet sont inclus entourés de citations régulières, par exemple #include "maclasse.h". Cette connexion des bibliothèques est standard. Par exemple, dans Studio visuel Le non-respect de cette norme entraînera des erreurs.
  2. norme est l'utilisation de l'espace de noms dans lequel se trouve l'instruction de sortie cout. Les espaces de noms ont été introduits en C++ afin de supprimer les conflits de noms entre les bibliothèques et le projet du développeur s'il existe des noms de fonctions ou de classes en double quelque part. Java utilise un système de packages pour résoudre les conflits de noms.

    cout est un opérateur de sortie qui a un opérateur surchargé << pour éviter d'utiliser une fonction distincte pour afficher du texte sur la console.

Ceci en plus de l'écriture de la fonction principal peut prendre différentes formes, bien que deux entrées soient standard :

  1. int principal()
  2. int principal (int argc, char* argv)

Vous pouvez également trouver des enregistrements comme vide principal() etc. Mais ce sont des entrées erronées, même si dans certains compilateurs, elles seront compilées même sans erreurs ni avertissements.

Enregistré int principal (int argc, char* argv) les arguments sont passés :

  1. argc- indique le nombre d'arguments passés. Toujours au moins 1, puisque le nom du programme est toujours transmis
  2. argv- un tableau de pointeurs vers des arguments passés sous forme de variables de chaîne.

Si argc supérieur à 1, cela signifie que des arguments supplémentaires ont été passés lors du lancement du programme.

Le chèque pourrait ressembler à ceci :

#inclure int main(int argc, char* argv) ( // Si un argument supplémentaire a été passé, if (argc > 1) ( // alors nous essaierons d'imprimer l'argument reçu std::cout<< argv<

En général, il y a un grand nombre de points qui doivent être compris en C++ même pour un petit programme, mais cela ne fait que le rendre plus intéressant ;-)

Un jour, je me suis intéressé au contenu de la pile de la fonction principale d'un processus sous Linux. J'ai fait quelques recherches et maintenant je vous présente le résultat.

Options pour décrire la fonction principale :
1. int main()
2. int principal(int argc, char **argv)
3. int principal(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, char **argv, char **env, char **apple)

Argc - nombre de paramètres
argv - tableau de pointeurs à terminal nul vers des chaînes de paramètres de ligne de commande
env est un tableau de pointeurs à terminal nul vers des chaînes de variables d'environnement. Chaque ligne au format NAME=VALUE
auxv - tableau de valeurs auxiliaires (disponible uniquement pour PowerPC)
apple - chemin d'accès au fichier exécutable (sous MacOS et Darwin)
Un vecteur auxiliaire est un tableau contenant diverses informations supplémentaires, telles que l'identifiant d'utilisateur effectif, l'attribut de bit setuid, la taille de la page mémoire, etc.

La taille du segment de pile peut être trouvée dans le fichier maps :
chat /proc/10918/maps

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

Avant que le chargeur ne transfère le contrôle à main, il initialise le contenu des tableaux de paramètres de ligne de commande, de variables d'environnement et de vecteur auxiliaire.
Après initialisation, le haut de la pile ressemble à ceci pour la version 64 bits.
Adresse senior en haut.

1. 0x7ffffffff000 Le point supérieur du segment de pile. L'appel provoque une erreur de segmentation
0x7ffffffff0f8 NUL vide* 8 0x00"
2. nom de fichier carboniser 1+ "/tmp/a.out"
carboniser 1 0x00
...
env carboniser 1 0x00
...
carboniser 1 0x00
3. 0x7ffffffffe5e0 env carboniser 1 ..
carboniser 1 0x00
...
argv carboniser 1 0x00
...
carboniser 1 0x00
4. 0x7ffffffffe5be argv carboniser 1+ "/tmp/a.out"
5. Tableau de longueur aléatoire
6. données pour auxv vide* 48"
AT_NULL Elf64_auxv_t 16 {0,0}
...
auxv Elf64_auxv_t 16
7. auxv Elf64_auxv_t 16 Ex. : (0x0e,0x3e8)
NUL vide* 8 0x00
...
env carboniser* 8
8. 0x7ffffffffe308 env carboniser* 8 0x7ffffffffe5e0
NUL vide* 8 0x00
...
argv carboniser* 8
9. 0x7ffffffffe2f8 argv carboniser* 8 0x7ffffffffe5be
10. 0x7ffffffffe2f0 argc long int 8" nombre d'arguments + 1
11. Variables locales et arguments des fonctions appelées avant main
12. Variables locales principales
13. 0x7ffffffffe1fc argc int 4 nombre d'arguments + 1
0x7ffffffffe1f0 argv carboniser** 8 0x7ffffffffe2f8
0x7ffffffffe1e8 env carboniser** 8 0x7ffffffffe308
14. Variables de fonction locales

" - Je n'ai pas trouvé de descriptions des champs dans les documents, mais ils sont bien visibles dans la décharge.

Je n'ai pas vérifié 32 bits, mais il suffit probablement de diviser les tailles par deux.

1. L'accès aux adresses au-dessus du point supérieur provoque un Segfault.
2. Une chaîne contenant le chemin d'accès au fichier exécutable.
3. Tableau de chaînes avec variables d'environnement
4. Tableau de chaînes avec paramètres de ligne de commande
5. Tableau de longueur aléatoire. Sa sélection peut être désactivée avec les commandes
sysctl -w kernel.randomize_va_space=0
écho 0 > /proc/sys/kernel/randomize_va_space
6. Données pour le vecteur auxiliaire (par exemple, la chaîne « x86_64 »)
7. Vecteur auxiliaire. Plus de détails ci-dessous.
8. Tableau de pointeurs à terminal nul vers des chaînes de variables d'environnement
9. Tableau de pointeurs à terminal nul vers les chaînes de paramètres de ligne de commande
10. Un mot machine contenant le nombre de paramètres de ligne de commande (un des arguments des fonctions « majeures », voir paragraphe 11)
11. Variables locales et arguments des fonctions appelées avant main(_start,__libc_start_main..)
12. Variables déclarées dans main
13.Arguments de la fonction principale
14. Variables et arguments des fonctions locales.

Vecteur auxiliaire
Pour i386 et x86_64, il n'est pas possible d'obtenir l'adresse du premier élément du vecteur auxiliaire, mais le contenu de ce vecteur peut être obtenu par d'autres moyens. L'une d'elles consiste à accéder à la zone mémoire située immédiatement derrière le tableau de pointeurs vers des chaînes de variables d'environnement.
Cela devrait ressembler à ceci :
#inclure #inclure int main(int argc, char** argv, char** env)( Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); //recherche du début du vecteur auxiliaire pour ( auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++)( printf("addr: %p type: %lx is: 0x%lx\n", auxv, auxv->a_type, auxv->a_un .a_val); ) printf("\n (void*)(*argv) - (void*)auxv= %p - %p = %ld\n (void*)(argv)-(void* )(&auxv) =%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void* )(argv) , (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); printf("\n copie argc : %d\n",*((int *) (argv - 1 ))); renvoie 0 ;
Les structures Elf(32,64)_auxv_t sont décrites dans /usr/include/elf.h. Fonctions de remplissage des structures dans linux-kernel/fs/binfmt_elf.c

Deuxième façon d'obtenir le contenu d'un vecteur :
vidage hexadécimal /proc/self/auxv

La représentation la plus lisible est obtenue en définissant la variable d'environnement LD_SHOW_AUXV.

LD_SHOW_AUXV=1 ls
AT_HWCAP : bfebfbff //capacités du processeur
AT_PAGESZ : 4096 //taille de la page mémoire
AT_CLKTCK : 100 //temps de fréquence de mise à jour()
AT_PHDR : 0x400040 //informations d'en-tête
AT_PHENT : 56
AT_PHNUM : 9
AT_BASE : 0x7fd00b5bc000 //adresse de l'interprète, c'est-à-dire ld.so
AT_FLAGS : 0x0
AT_ENTRY : 0x402490 //point d'entrée du programme
AT_UID : 1000 //identifiants d'utilisateur et de groupe
AT_EUID : 1000 //nominal et effectif
AT_GID : 1 000
AT_EGID : 1000
AT_SECURE : 0 //si l'indicateur setuid est levé
AT_RANDOM : 0x7fff30bdc809 //adresse de 16 octets aléatoires,
généré au démarrage
AT_SYSINFO_EHDR : 0x7fff30bff000 //pointeur vers la page utilisée pour
//appels système
AT_EXECFN : /bin/ls
AT_PLATFORM : x86_64
A gauche se trouve le nom de la variable, à droite se trouve la valeur. Tous les noms de variables possibles et leurs descriptions peuvent être trouvés dans le fichier elf.h. (constantes avec préfixe AT_)

Retour de main()
Une fois le contexte du processus initialisé, le contrôle n'est pas transféré à main(), mais à la fonction _start().
main() est déjà appelé depuis __libc_start_main. Cette dernière fonction a une fonctionnalité intéressante : on lui passe un pointeur vers une fonction qui doit être exécutée après main(). Et ce pointeur passe naturellement à travers la pile.
En général, les arguments de __libc_start_main ressemblent à ceci, selon le fichier glibc-2.11/sysdeps/ia64/elf/start.S
/*
* Arguments pour __libc_start_main :
* out0 : principal
* sortie1 : argc
* sortie2 : argv
*out3 : initialisation
* out4: fini //fonction appelée après main
*out5 : rtld_fini
* out6 : stack_end
*/
Ceux. pour obtenir l'adresse du pointeur fini, vous devez décaler deux mots machine de la dernière variable locale main.
Voici ce qui s'est passé (la fonctionnalité dépend de la version du compilateur) :
#inclure annuler **ret; annuler *congé; void foo())( void (*boo)(void); //pointeur de fonction printf("Stack rewrite!\n"); boo = (void (*)(void))leave; boo(); // fini () ) int main(int argc, char *argv, char *envp) ( unsigned long int mark = 0xbfbfbfbfbfbfbfbf; //marque à partir de laquelle nous allons travailler ret = (void**)(&mark+2); // extraire le adresse , la fonction appelée après l'achèvement (fini) Leave = *ret; // rappelez-vous *ret = (void*)foo; // return 0;

J'espère que c'était intéressant.
Bonne chance.

Merci à l'utilisateur Xeor pour le conseil utile.

Vous pouvez transmettre certains arguments aux programmes C. Lorsque main() est appelé au début d’un calcul, trois paramètres lui sont transmis. Le premier d'entre eux détermine le nombre d'arguments de commande lors de l'accès au programme. Le second est un tableau de pointeurs vers des chaînes de caractères contenant ces arguments (un argument par ligne). Le troisième est également un tableau de pointeurs vers des chaînes de caractères ; il permet d'accéder aux paramètres du système d'exploitation (variables d'environnement).

Une telle ligne est représentée par :

variable = valeur\0

La dernière ligne peut être trouvée grâce aux deux zéros à droite.

Nommons les arguments de la fonction main() en conséquence : argc, argv et env (tous les autres noms sont possibles). Les descriptions suivantes sont alors acceptables :

principal(int argc, char *argv)

principal(int argc, char *argv, char *env)

Supposons que sur le lecteur A : il y ait un programme prog.exe. Abordons-le comme suit :

A:\>prog.exe fichier1 fichier2 fichier3

Alors argv est un pointeur vers la ligne A:\prog.exe, argv est un pointeur vers la ligne file1, etc. Le premier argument réel est pointé par argv et le dernier par argv. Si argc=1, alors il n'y a aucun paramètre après le nom du programme sur la ligne de commande. Dans notre exemple, argc=4.

Récursion

La récursivité est une méthode d'appel dans laquelle une fonction se réfère à elle-même.

Un point important lors de la composition d'un programme récursif est l'organisation de la sortie. Une erreur facile à commettre ici est que la fonction s’appellera séquentiellement indéfiniment. Par conséquent, le processus récursif doit, étape par étape, simplifier le problème afin qu’il y ait finalement une solution non récursive. L'utilisation de la récursivité n'est pas toujours souhaitable, car elle peut entraîner un débordement de pile.

Fonctions de la bibliothèque

Dans les systèmes de programmation, les routines permettant de résoudre les problèmes fréquemment rencontrés sont regroupées dans des bibliothèques. Ces tâches comprennent : le calcul de fonctions mathématiques, l'entrée/sortie de données, le traitement de chaînes, l'interaction avec les outils du système d'exploitation, etc. L'utilisation de routines de bibliothèque évite à l'utilisateur de développer des outils appropriés et lui fournit des services supplémentaires. Les fonctions incluses dans les bibliothèques sont fournies avec le système de programmation. Leurs déclarations sont données dans des fichiers *.h (c'est ce qu'on appelle des fichiers d'inclusion ou d'en-tête). Par conséquent, comme mentionné ci-dessus, au début d'un programme avec des fonctions de bibliothèque, il devrait y avoir des lignes telles que :

#inclure<включаемый_файл_типа_h>

Par exemple:

#inclure

Il existe également des fonctionnalités permettant d'étendre et de créer de nouvelles bibliothèques avec des programmes utilisateur.

Les variables globales se voient attribuer une place fixe en mémoire pour toute la durée du programme. Les variables locales sont stockées sur la pile. Entre eux se trouve une zone mémoire pour l'allocation dynamique.

Les fonctions malloc() et free() sont utilisées pour allouer dynamiquement de la mémoire libre. La fonction malloc() alloue de la mémoire, la fonction free() la libère. Les prototypes de ces fonctions sont stockés dans le fichier d'en-tête stdlib.h et ressemblent à :

void *malloc(size_t taille);

vide *gratuit(void *p);

La fonction malloc() renvoie un pointeur vide ; Pour une utilisation correcte, la valeur de la fonction doit être convertie en un pointeur vers le type approprié. En cas de succès, la fonction renvoie un pointeur vers le premier octet de mémoire libre de taille size. S'il n'y a pas assez de mémoire, la valeur 0 est renvoyée. Pour déterminer le nombre d'octets nécessaires pour une variable, utilisez l'opération sizeof().

Un exemple d'utilisation de ces fonctions :

#inclure

#inclure

p = (int *) malloc(100 * sizeof(int)); /* Allouer de la mémoire pour 100

entiers */

printf("Pas assez de mémoire\n");

pour (je = 0; je< 100; ++i) *(p+i) = i; /* Использование памяти */

pour (je = 0; je< 100; ++i) printf("%d", *(p++));

gratuit(p);

/* Mémoire libre */

Avant d'utiliser le pointeur renvoyé par malloc(), vous devez vous assurer qu'il y a suffisamment de mémoire (le pointeur n'est pas nul).

Préprocesseur

Un préprocesseur C est un programme qui traite les entrées du compilateur. Le préprocesseur examine le programme source et effectue les actions suivantes : y connecte les fichiers spécifiés, effectue des substitutions et contrôle également les conditions de compilation. Les lignes de programme commençant par le symbole # sont destinées au préprocesseur. Une seule commande (directive du préprocesseur) peut être écrite sur une seule ligne.

Directif

#define substitution d'identifiant

provoque le remplacement de l'identifiant nommé dans le texte du programme suivant par le texte de substitution (notez l'absence de point-virgule à la fin de cette commande). Essentiellement, cette directive introduit une définition de macro, où « identifiant » est le nom de la définition de macro et « substitution » est la séquence de caractères par laquelle le préprocesseur remplace le nom spécifié lorsqu'il le trouve dans le texte du programme. Il est d'usage de taper le nom d'une définition de macro en majuscules.

Regardons des exemples :

Notez que puisque le préprocesseur ne vérifie pas la compatibilité entre les noms symboliques des définitions de macro et le contexte dans lequel elles sont utilisées, il est recommandé de définir de tels identifiants non pas avec la directive #define, mais à l'aide du mot-clé const avec un indication explicite du type (cela s'applique davantage au C++) :

const entier MAX = 25 ;

(le type int peut être omis, car c'est la valeur par défaut).

Si la directive #define ressemble à :

#define identifiant(identifiant, ..., identifiant)​substitution

et il n'y a pas d'espace entre le premier identifiant et la parenthèse ouvrante, alors c'est la définition d'une macro substitution avec arguments. Par exemple, après une ligne comme :

#define READ(val) scanf("%d", &val)

instruction READ(y); traité de la même manière que scanf("%d",&y);. Ici val est un argument et une substitution de macro avec l'argument est effectuée.

S'il y a de longues définitions dans la substitution qui se poursuivent dans la ligne suivante, le caractère \ est placé à la fin de la ligne de continuation suivante.

Vous pouvez placer des objets séparés par ## dans une définition de macro, par exemple :

#définir PR(x, y) x##y

Après cela, PR(a, 3) appellera la substitution a3. Ou, par exemple, la définition d'une macro

#définir z(a, b, c, d) a(b##c##d)

entraînera le remplacement de z(sin, x, +, y) par sin(x+y).

Le symbole # placé avant un argument de macro indique qu'il est converti en chaîne. Par exemple, après la directive

#define PRIM(var) printf(#var"= %d", var)

le fragment suivant du texte du programme

est converti comme ceci :

printf("année""= %d", année);

Décrivons d'autres directives du préprocesseur. La directive #include a déjà été vue. Il peut être utilisé sous deux formes :

#include "nom de fichier"

#inclure<имя файла>

L'action des deux commandes consiste à inclure les fichiers portant le nom spécifié dans le programme. Le premier d'entre eux charge un fichier depuis le répertoire courant ou le répertoire spécifié en préfixe. La deuxième commande recherche le fichier dans des emplacements standards définis dans le système de programmation. Si le fichier dont le nom est écrit entre guillemets doubles n'est pas trouvé dans le répertoire spécifié, alors la recherche se poursuivra dans les sous-répertoires spécifiés pour la commande #include<...>. Les directives #include peuvent être imbriquées les unes dans les autres.

Le groupe de directives suivant vous permet de compiler sélectivement des parties du programme. Ce processus est appelé compilation conditionnelle. Ce groupe comprend les directives #if, #else, #elif, #endif, #ifdef, #ifndef. La forme de base d'écriture de la directive #if ressemble à ceci :

#if constante_expression séquence_of instructions

Ici, la valeur d'une expression constante est vérifiée. Si c'est vrai, alors la séquence d'instructions spécifiée est exécutée, et si c'est faux, alors cette séquence d'instructions est ignorée.

L'action de la directive #else est similaire à l'action de la commande else en langage C, par exemple :

#ifexpression_constante

instruction_sequence_2

Ici, si l'expression constante est vraie, alors Operator_sequence_1 est exécuté, et si faux, Operator_sequence_2 est exécuté.

La directive #elif signifie une action "else if". La forme de base de son utilisation est la suivante :

#ifexpression_constante

séquence_instruction

#elif expression_constante_1

instruction_sequence_1

#elif constante_expression_n

séquence_of_statements_n

Cette forme est similaire à la construction du langage C : if...else if...else if...

Un préprocesseur C est un programme qui traite les entrées du compilateur. Le préprocesseur examine le programme source et effectue les actions suivantes : y connecte les fichiers spécifiés, effectue des substitutions et contrôle également les conditions de compilation. Les lignes de programme commençant par le symbole # sont destinées au préprocesseur. Une seule commande (directive du préprocesseur) peut être écrite sur une seule ligne.

Identifiant #ifdef

détermine si l'identifiant spécifié est actuellement défini, c'est-à-dire s'il a été inclus dans des directives comme #define. Chaîne du formulaire

Identifiant #ifndef

vérifie si l'identifiant spécifié est actuellement indéfini. Chacune de ces directives peut être suivie d'un nombre arbitraire de lignes de texte, contenant éventuellement une instruction #else (#elif ne peut pas être utilisé) et se terminant par la ligne #endif. Si la condition vérifiée est vraie, alors toutes les lignes entre #else et #endif sont ignorées, et si fausse, alors les lignes entre la vérification et #else (s'il n'y a pas de mot #else, alors #endif). Les directives #if et #ifndef peuvent être imbriquées les unes dans les autres.

Afficher la directive

Identifiant #undef

fait que l'identifiant spécifié est considéré comme indéfini, c'est-à-dire non soumis à remplacement.

Regardons des exemples. Les trois directives suivantes :

vérifiez si l'identifiant WRITE est défini (c'est-à-dire s'il y a eu une commande comme #define WRITE...), et si c'est le cas, alors le nom WRITE commence à être considéré comme indéfini, c'est-à-dire non soumis à remplacement.

Directives

#define ÉCRIRE fprintf

vérifie si l'identifiant WRITE n'est pas défini, et si c'est le cas, l'identifiant WRITE est déterminé à la place du nom fprintf.

La directive #error s'écrit sous la forme suivante :

#erreur message_erreur

Si cela se produit dans le texte du programme, la compilation s'arrête et un message d'erreur s'affiche sur l'écran d'affichage. Cette commande est principalement utilisée lors de la phase de débogage. Notez que le message d’erreur n’a pas besoin d’être placé entre guillemets.

La directive #line est destinée à modifier les valeurs des variables _LINE_ et _FILE_ définies dans le système de programmation C. La variable _LINE_ contient le numéro de ligne du programme en cours d'exécution. L'identifiant _FILE_ est un pointeur vers une chaîne avec le nom du programme en cours de compilation. La directive #line s'écrit comme suit :

#numéro de ligne "nom_fichier"

Ici, le nombre est n'importe quel entier positif qui sera attribué à la variable _LINE_, file_name est un paramètre facultatif qui remplace la valeur de _FILE_.

La directive #pragma permet de passer certaines instructions au compilateur. Par exemple, la ligne

indique que le programme C contient des chaînes en langage assembleur. Par exemple:

Examinons quelques identifiants globaux ou noms de macro (noms de définition de macro). Cinq de ces noms sont définis : _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Deux d'entre eux (_LINE_ et _FILE_) ont déjà été décrits ci-dessus. L'identifiant _DATE_ spécifie une chaîne qui stocke la date à laquelle le fichier source a été traduit en code objet. L'identifiant _TIME_ spécifie une chaîne qui stocke l'heure à laquelle le fichier source a été traduit en code objet. La macro _STDC_ a une valeur de 1 si des noms de macro définis standard sont utilisés. Sinon cette variable ne sera pas définie.



Des questions ?

Signaler une faute de frappe

Texte qui sera envoyé à nos rédacteurs :