Fonctions

Les fonctions permettent d’encapsuler du code dans une region précise du programme pour y référer ensuite par leur nom.

Un appel de fonction consiste à l’écrire et à lui passer les paramètres voulus entre parenthèses. Cette systaxe est proche de la notation mathématique d’un appel de fonction a = f(x), de la même manière la description du d’une fonction emprunte à notation d’un ensemble en extension 🌍⤴ où chaque élément serait séparé par des points-virgules. La comparaison s’arrête ici car en C toutefois les instructions sont ordonnées et peuvent être dupliquées ce qui les rapprocherait davantage des uplets 🌍⤴ .

Les fonctions avec un nombre d’arguments inférieur ou égale à 4 sont optimisées de sorte que les paramètres soient passés par les registres du processeur. Lorsqu’une fonction est suffisamment petite elle peut être intégrée dans le code qui l’appellé (inlinée).

Les fonctions longues et non inlinables génèrent un cout elevé en terme d’exécution de l’ordre de plusieurs magnitudes, à cause du saut, de la mise en mémoire du lieu d’appel, etc.

Une suite récursive d’appels de fonction expose à un stack overflow si l’algorithme n’est pas logarithmique.

Par héritage avec le C de K&R, une fonction ne déclarant rien entre ses parenthèses prend un nombre inspécifié d’arguments, aujourd’hui on recommande d’écrire une telle fonction de la sorte

int function(...) {}

Les trois points, ou ellipse, indiquent que cette fonction accepte un nombre indéterminé de paramètres , jusqu’a une limite définie par l’implémentation.

Dans la fonction main l’instruction return n’est pas nécessaire, une fonction main lorsqu’elle se termine retourne la valeur qui correspond à une exécution normale du programme (généralement celle-ci est zero : 0). Une valeur différente de zero correspond à une erreur, la valeur exacte donne des précisions sur l’erreur en question. Chaque systeme possède ses propres valeurs d’erreur, mais une valeur zero comme garante de succès est généralement normal et protable, mais par portabilité maximale il est préférable de na pas la mettre du tout.

Les deux fonctions main qui suivent sont identiques.

int main(void) {
    return 0;
}

int main(void) {
    
}

En C la fonction main() peut s’appeller elle-même, cela est interdit en C++.

todo: bien expliquer la recursion terminale, pourquoi le tri fusion ne peut se faire avec

Un tableau lorsqu’il est évalué est converti en pointeur de type de ce qu’il contient, ainsi passer un tableau en parametre de fonction consiste à passer un pointeur sur celui-ci.

en parler dans la section tableau

La fonction ne connait rien du tableau originel hormis son type et son adresse, l’opérateur sizeof sur le pointeur renverra la taille d’un pointeur générique et non la taille du tableau originel, la fonction ne peut donc pas connaitre la taille du tableau et il faut soit la passer comme paramètre supplémentaire ou bien mettre un avaleur sentinelle dans celui-ci, comme c’est le cas des chaines de caractère.

unsigned len1(char*tab)

int main(void) {
    int tab[] = {'t', 'o', 't', 'o', '\0'};

    len(tab);
    len(&tab); // identique
}

danger de retourner un ppinteur sur une variable interne

Contrairement aux tableaux, les structures sont passées aux fonctions en tant que valeurs copiées et non comme pointeurs, modifier une structure dans une fonction est donc inutile et il est indispensable de la passer comme pointeur.

struct toto {
    int a;
    char b[5];
    struct toto c;
}

void fun(struct toto * toto) {
    
    (*toto).a = 2;
    toto->a = 2; // identique 


    (*(*toto).b) = 'b';
    toto->b[0] = 'b' // identique


    (*(*(*toto).c).b) = 'd';²
    toto->c->b[0] = 'd'; // identique
}

int main(void) {
    struct toto toto ={1, "toto", {2, "tata"}};

    fun(&toto);

    printf("%d\n", toto.a); // 2

    printf("%s\n", toto.b); // boto

    printf("%s", toto.c.b); // doto
}

todo alleger exemple ci dessus

recursion terminale

passage de fonctions en appro

todo : parler de variables globales en appro

parler vatiables statiqies en appro

La fonction main() est appellée par le système d’exploitation et sa signature pourrait être ce qu’on veut, la norme spécifie deux signatures qui assurent une compatibilité maximale avec la pluspart des systèmes


int main(void) {}
int main(int argc, char*argv[]) {}

La première forme ignore les paramètres fournis a la fonction, la deuxieme permet au programme d’accéder aux paramètres d’appel au programme.

Imaginons que le programme toto.exe soit dans le dossier /home et que nous l’executions ainsi /home/toto.exe titi tata

alors argc (arguments count) vaudra 3 : ce qui correspond au nombre d’arguments fournis au programme plus un, argv (arguments values) est un tableau de pointeurs vers les elemenrs de la commande d’appel au programme : ici argv[0] vaut /home/toto, argv[1] vaut titi, argv[2] pointe sur tata et argv[3] est un pointeur nul.

Il y a deux facons d’afficher les arguments de notre programme

int main(int argc, char*argv[]) {

    printf("%d", argc); // 3

    for(int i = 0; i < arg; i++) {
        prinf("%s", argv[i]);
    }

    char **ptr = argv;
    while(*ptr) {
        printf("%s", **ptr);
        (*ptr)++;
    }
}