Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

(je suis débutant en c) je ne vois d'instruction pour boucler

156 réponses
Avatar
bpascal123
Bjr,sr,
Voici une fonction tr=E8s simple qui r=E9agit comme s'il y avait une
boucle alors qu'en apparence il n'y en a pas :

#include <stdio.h>

void f2(int b);
void f1(int a);

int main(void)
{
f1(30);

return 0;
}

void f1(int a)
{
if( a )
f2( a - 1 );
printf("%d ", a);
}

void f2(int b)
{
printf(" . ");
if(b)
f1( b - 1 );
}

http://www.java2s.com/Code/C/Function/Functioncalleachother.htm

Merci,
Pascal

10 réponses

Avatar
candide
Wykaaa a écrit :


Je te donne un autre exemple encore :
Ecris un évaluateur de lambda-expressions de façon non récursive...

Il y a quantité d'algorithme qu'il est EXTREMEMENT plus simple d'écrire
en récursif, même et surtout si on élimine les algos sur les arbres, les
listes, etc. qui pourtant, ne sont pas que des exercices de style
puisqu'ils sont utilisés dans les optimiseurs des compilateurs, les
systèmes d'exploitation, etc.




Mais je ne dis pas que la récursivité soit un concept inutile. Remettons
le fil dans son contexte. pascal (le PO) essaye de déchiffrer un code
récursif tordu alors qu'il a bien d'autres choses plus élémentaires à
régler. Le PO, tu sais il est très très loin des lambda-expression, déjà
s'il savait écrire de lui-même une fonction de son crû, il aurait avancé.
Avatar
candide
Richard Delorme a écrit :

Suites arithmétiques :
a_(0)
a_(n+1) = a_(n) + r

Suites géométriques :
q_(0)
q_(n+1) = q_(n) * r

etc.

Si l'on a compris ça, on a compris la récursivité.






Ce que tu nous donnes là, c'est une variation sur la sempiternelle
version de la factorielle : ta première fonction revient à calculer un
produit récursivement et la seconde une puissance récursivement, ce qui
au demeurant ne me parait pas naturel. Il me semble de plus naturel de
dire à un collégien que 12**42 c'est le produit de 12 quarante deux
fois par lui-même que de lui dire qu'une formule comme 12**truc c'est
12**(truc-1)*12 si truc !=0 et que 12**0 vaut 1.


Par ailleurs, je crois que les lycéens voient ça mais c'est surtout
l'occasion de leur parler de raisonnement par récurrence.


Maintenant, je pense que tu as plein de gens qui ont compris la version
récursive de la factorielle et qui seront incapables de te coder (en C)
récursivement toutes les façons de combiner par exemple trois objets,
par exemple si les objets sont

entree, plat, dessert


ça doit générer

entre
plat
dessert
entree plat
entree dessert
plat dessert
entree plat dessert

Ce que je peux te dire, c'est qu'une majorité d'étudiants va savoir
coder assez facilemnt une puissance en récursif mais très très peu
sauront écrire le code récursif de génération des combinaisons ou
quelque chose du genre.

Je peux même te donner un exemple concret : l'année dernière en contrôle
de TP, on a demandé de coder en récursif le calcul de la longueur d'une
chaîne. Pas compliqué Et pourtant, voici un exemple de code qu'une
étudiante (en maths) sérieuse a donné :



#include <stdio.h>

int f(char *phrase)
{
int i;
int cpt;
if(phrase[i]='')cpt=0;
/*on decale de une lettre a chaque appel c'est à dire que
par exemple on compte la première lettre puis le nombre de lettre
de la phrase sans compter la première. dans notre cas
on compte 1 (pour le j) + le nombre de lettre de
"e hais le C"(sans le j) et ainsi de suite jusqu'a ce
qu'on tombe sur le caractère nul.donc on doit prendre en compte
la phrase qui commence par le caractère de la case d'indice 1,puis 2 ...*/
else cpt=1+f(?);
return cpt;

}

int main(void)
{
char phrase[]="je hais le C";
int compteur=f(phrase);
printf("le nombre de caractère est de :%d",compteur);

return 0;
}




Voici un autre exemple d'un étudiant pourtant sérieux et assidu :


#include <stdio.h>

int f(char *chaine)
{
int res;

while (chaine != '')
{
res=1+f(chaine-1);
}

return res;
}

int main(void)
{
char s[]="je hais la C!";

printf ("il y a %d caractères.n",f(s));

return 0;
}






Donc, le concept de récursivité il est assez facile à comprendre mais il
faut aller au-delà du concept, "In code we trust!" comme on dit. Et on
se rend compte que cela peut poser certaines difficultés
d'implémentation (en C par exemple mais je suppose que ça doit être vrai
dans d'autres langages) si on veut être un minimum efficace, il n'est
pas toujours facile de voir comment faire pour que chaque appel n'alloue
pas les données et qu'au contraire, ils utilisent une sorte de pool
commun. Si en général, on suit l'algorithme mathématique de trop près,
en général, on obtient des implémentations non efficaces.
Avatar
Samuel Devulder
Marc Espie a écrit :
In article ,
Erwan David wrote:

ça c'est mon historique de développeur embarqué qui parle...



Ca veut juste dire que tu optimises la recursion terminale a la main
en presence de compilateurs deficients...



Un truc a savoir, c'est que si l'optimiseur vire une récursivité, ton
prog devient nettement moins débuggable par perte du contexte courant
("stack frame"). En théorie on s'en fiche, mais à l'usage c'est chiant
et il vaut mieux que le compilo garde la structure asm assez proche du
code d'origine.

Je te raconte pas les surprise qu'on trouve en débuggant un code
optimisé par un compilo qui en fait trop. Par exemple au niveau
couverture du code on se retrouve à passer par les deux branches d'un
if() simultanément et crois moi c'est pas bon du tout pour certains
critères de couverture du code (mc/dc etc). En plus quand le code ASM ne
trace plus les lignes du code C dans le même ordre où elle sont écrite
rend le truc in-traçable. En théorie on s'en fiche d'avoir un code non
tracable s'il est bon.. mais voila le moindre code, meme hyper simple,
est souvent buggé (cf les pbs d'overflow en arithmetique) et on est
content d'avoir un code ASM assez proche du code C.

Dernier point: en embarque la vitesse n'est pas le problème. Les mhz
sont souvent là plus que nécéssaire (80Mhz pour traiter des trucs qui
arrivent à une freq de 5khz ca le fait les doigts dans le nez), mais ce
qui coince c'est la ROM et la RAM. Elles sont toutes petites en
embarqué, et on préfère de loin, mais vraiment de loin, optimiser en
taille (rom/ram) qu'en vitesse.

sam.
Avatar
Wykaaa
candide a écrit :

[snip]


Donc, le concept de récursivité il est assez facile à comprendre mais il
faut aller au-delà du concept, "In code we trust!" comme on dit. Et on
se rend compte que cela peut poser certaines difficultés
d'implémentation (en C par exemple mais je suppose que ça doit être vrai
dans d'autres langages) si on veut être un minimum efficace, il n'est
pas toujours facile de voir comment faire pour que chaque appel n'alloue
pas les données et qu'au contraire, ils utilisent une sorte de pool
commun. Si en général, on suit l'algorithme mathématique de trop près,
en général, on obtient des implémentations non efficaces.



C'est quoi un "pool commun" ?
Des variables globales ? statiques ?

Quand on utilise la récursivité, on va éviter les variables justement
(on n'utilisera que des paramètres). On ne va faire que des appels de
fonctions (récursivement). C'est évidemment possible en C.

La programmation fonctionnelle, tu connais ?
Les langages sans affectation, tu connais ?
Les langages à assignation unique, tu connais ?
L'évaluation paresseuse, tu connais ?

Fonction d'Ackermann en Haskell :
ackermann 0 n = (n + 1)
ackermann m 0 = ackermann (m-1) 1
ackermann m n = ackermann (m-1) (ackermann m (n-1))

Justement, le mieux c'est de suivre, au plus près, la définition
mathématique.
Avatar
candide
Wykaaa a écrit :

C'est quoi un "pool commun" ?
Des variables globales ? statiques ?



En pratique c'est souvent le cas mais on peut aussi passer en paramètre
un pointeur vers ce qui va être le pool.


Quand on utilise la récursivité, on va éviter les variables justement
(on n'utilisera que des paramètres).




Regarde un implémentation correcte de qsort() par un quicksort et tu
verras qu'il n'y a pas que des paramètres. J'ai celle de Plauger sous
les yeux (c'est pas vraiment un mauvais programmeur!) et il définit un
paquet de variables. Au final, c'est pas très gênant vu que la
complexité est en n*ln(n) donc on limite la casse.


On peut bien sûr éviter les variables mais alors on introduit des
paramètres factices, qui ne servent qu'au fonctionnement interne de la
fonction, bref on mélange interface et implémentation, le truc que je
déteste.






On ne va faire que des appels de
fonctions (récursivement). C'est évidemment possible en C.



Oui, tu enfonces des portes ouvertes.



La programmation fonctionnelle, tu connais ?



Plusieurs de mes collègues ont tenté de m'y convertir (enfin à Caml)
mais en vain.


Les langages sans affectation, tu connais ?



Non

Les langages à assignation unique, tu connais ?



Non

L'évaluation paresseuse, tu connais ?




Lazy evaluation ? j'ai vu le nom dans des docs Python mais je ne sais
pas ce que c'est.






Fonction d'Ackermann en Haskell :
ackermann 0 n = (n + 1)
ackermann m 0 = ackermann (m-1) 1
ackermann m n = ackermann (m-1) (ackermann m (n-1))

Justement, le mieux c'est de suivre, au plus près, la définition
mathématique.



Certainement pas. Exemple classique :

int f(int n)
{
return n <= 1 ? 1 : f(n - 1) + f(n - 2);
}


ce qui une fois de plus montre qu'il y a une distance entre le concept
souvent simple voire trivial, et l'implémentation. Bref, ne confondons
pas récursivité de salon et récursivité de codeur.

Au demeurant, peut-être que certains certains langages sont plus aptes
que d'autres à suivre au plus près la définition mathématique.
Avatar
espie
In article <4b12a48c$0$9391$,
Samuel Devulder wrote:
Marc Espie a écrit :
In article ,
Erwan David wrote:

ça c'est mon historique de développeur embarqué qui parle...



Ca veut juste dire que tu optimises la recursion terminale a la main
en presence de compilateurs deficients...





Un truc a savoir, c'est que si l'optimiseur vire une récursivité, ton
prog devient nettement moins débuggable par perte du contexte courant
("stack frame"). En théorie on s'en fiche, mais à l'usage c'est chiant
et il vaut mieux que le compilo garde la structure asm assez proche du
code d'origine.



En general, t'as un ou deux drapeaux dans ton compilo pour choisir quel
comportement adopter... et sauf code produit tres vite et rempli de bugs,
je prend toujours le cote "plus optimise".

Je te raconte pas les surprise qu'on trouve en débuggant un code
optimisé par un compilo qui en fait trop.



T'as pas besoin, je pense que je connais aussi bien que toi... tant que
le compilo lui-meme n'est pas buggue, c'est pas un souci.

Apres tout, on est dans fr.comp.lang.c, donc la notion de comportement
observable, c'est dans le sujet. Et si on ne maitrise pas ce genre
de choses, ben il faut rester loin d'un compilo optimisant (tu veux
qu'on lance une flamewar sur restrict et sur les regles d'aliasing en C ?...)

Par exemple au niveau
couverture du code on se retrouve à passer par les deux branches d'un
if() simultanément et crois moi c'est pas bon du tout pour certains
critères de couverture du code (mc/dc etc). En plus quand le code ASM ne
trace plus les lignes du code C dans le même ordre où elle sont écrite
rend le truc in-traçable. En théorie on s'en fiche d'avoir un code non
tracable s'il est bon.. mais voila le moindre code, meme hyper simple,
est souvent buggé (cf les pbs d'overflow en arithmetique) et on est
content d'avoir un code ASM assez proche du code C.



Ca, par contre, les soucis d'overflow en arithmetique, ca empeche souvent
un gros ensemble d'optimisations naturelles, surtout si tu es dans un
environnement ou les overflow signalent. Mais bon, la-aussi tu sais ca
aussi bien que moi.

Perso, si je voulais faire de l'asm, je ferais de l'asm. J'attend de mon
compilo qu'il fasse un boulot decent cote optimisation. Je suis conscient
que ca n'est pas toujours le cas sur certaines archis peu repandues, helas...
Avatar
Samuel Devulder
Marc Espie a écrit :

> En general, t'as un ou deux drapeaux dans ton compilo pour choisir quel
> comportement adopter... et sauf code produit tres vite et rempli de bugs,
> je prend toujours le cote "plus optimise".

Oui. Mais je suis tombé un jour sur une exécution pas a pas d'un code
bien trop optimisé au point de se demander si c'était effectivement le
bon fichier C qu'on exécutait tellement le flot du programme observé
était différent de celui codé. Je serais tenté comme toi de dire qu'on
s'en fiche, qu'il faut faire confiance au compilo, certes, mais le pb
ici était dans les #define présent dans le code, donc de déterminer la
configuration du programme était la bonne ou pas. En 1ere approche je
préfère que le compilo ne joue pas trop avec la structure du code. On
s'y retrouve mieux dans la maintenance.

C'est un problème uniquement pratique, je te l'accorde, mais important.
Si le programme a une récursivité terminale, qu'il la laisse.. car en
cas de pb, on sera bien contant d'utiliser la stack pour voir ce qu'il
se passe. Si on veut optimiser, alors on écrit une boucle à la place
soit même. C'était le propos de Erwan je pense. Il faut bien voir que
dans certains contextes on ne peut uploader du code aussi souvent qu'on
veut et qu'il faut faire avec ce qui est en rom.

Perso, si je voulais faire de l'asm, je ferais de l'asm. J'attend de mon
compilo qu'il fasse un boulot decent cote optimisation. Je suis conscient
que ca n'est pas toujours le cas sur certaines archis peu repandues, helas...



Oui. mais peu répandues... faut voire.. il y a quand meme quelques
dizaines d'ECU par voiture, ca en fait quelques millions de cpu à
l'archi peu répandue en circulation ;-) Tout est relatif au domaine en fait.

Non, comme je voulais le souligner, ce que chacun attends d'un compilo C
n'est pas identique suivant les professions.

Dans le domaine de l'embarqué, on ne peut faire de l'ASM à la main pour
des raisons de taille (les projets petits à moyens font quelques
dizaines de millions de lignes de codes à traiter par le compilo) et de
coût de maintenance. Du coup il y a quelques fonctions ASM très bas
niveau et beaucoup, mais alors beaucoup de C autour. L'important est ici
de faire tenir des millions de lignes de C dans quelques centaines de Ko.

On demande au compilo d'optimiser en vitesse, mais pas forcément au
détriment de la taille (enfin bon, on fait des compromis en pratique).
Quelque part on veut que le compilo compile comme on aurait écrit à la
main avec quelques astuces mais pas beaucoup plus car ça devient
infernal à tracer. Le fait que les compilos ne changent pas les flots
(données/contrôle) dans le programme n'est pas une preuve de leur
faiblesse, bien au contraire, mais un choix dicté par la pratique.

Du coup les optims qu'on trouve dans ces compilos spécialisés ne sont
pas les trucs type récursivité terminale, mais plutot des trucs propres
au cpu comme l'utilisation d'instruction compactes ou pré-cablées
(calculs min/max, ou interpolation linéaire).

Bref chaque domaine a des besoins differents d'un compilo C et il est
assez "injuste" de qualifier les uns ou les autre de mauvais parce
qu'ils supportent ou pas l'optimisation de recursivité terminale.

sam (c'est juste mon avis).
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :

C'est quoi un "pool commun" ?
Des variables globales ? statiques ?



En pratique c'est souvent le cas mais on peut aussi passer en paramètre
un pointeur vers ce qui va être le pool.




Quand on utilise la récursivité, on va éviter les variables justement
(on n'utilisera que des paramètres).




Regarde un implémentation correcte de qsort() par un quicksort et tu
verras qu'il n'y a pas que des paramètres. J'ai celle de Plauger sous
les yeux (c'est pas vraiment un mauvais programmeur!) et il définit un
paquet de variables. Au final, c'est pas très gênant vu que la
complexité est en n*ln(n) donc on limite la casse.


On peut bien sûr éviter les variables mais alors on introduit des
paramètres factices, qui ne servent qu'au fonctionnement interne de la
fonction, bref on mélange interface et implémentation, le truc que je
déteste.



C'est une question de modularité dans les fonctions. Je suis d'accord
que l'environnement doit être global.


On ne va faire que des appels de
fonctions (récursivement). C'est évidemment possible en C.



Oui, tu enfonces des portes ouvertes.



Non. Je voulais dire vraiment programmer en fonctionnel sans variable.


La programmation fonctionnelle, tu connais ?



Plusieurs de mes collègues ont tenté de m'y convertir (enfin à Caml)
mais en vain.



Quelle tristesse :-(


Les langages sans affectation, tu connais ?



Non

Les langages à assignation unique, tu connais ?



Non

L'évaluation paresseuse, tu connais ?




Lazy evaluation ? j'ai vu le nom dans des docs Python mais je ne sais
pas ce que c'est.



Alors je comprends que tu n'aime pas la récursivité.


Fonction d'Ackermann en Haskell :
ackermann 0 n = (n + 1)
ackermann m 0 = ackermann (m-1) 1
ackermann m n = ackermann (m-1) (ackermann m (n-1))

Justement, le mieux c'est de suivre, au plus près, la définition
mathématique.



Certainement pas. Exemple classique :

int f(int n)
{
return n <= 1 ? 1 : f(n - 1) + f(n - 2);
}



Oui la suite de Fibonacci écrite de cette manière n'est pas optimisée
car les mêmes évaluations se font à plusieurs endroits.

ce qui une fois de plus montre qu'il y a une distance entre le concept
souvent simple voire trivial, et l'implémentation. Bref, ne confondons
pas récursivité de salon et récursivité de codeur.



Ce n'est pas à moi que tu vas tenir ce discours. J'ai trempé pendant 10
ans dans les compilateurs et il y avait de la récursion à tous les
étages... Même pour le calcul d'adresse dans les générateurs de code.

L'optimiseur global faisait des transformations sur les arbres,
représentant le code du programme à compiler, que par des "patterns"
d'appels de fonctions récursives imbriquées !
Et crois-moi, ce n'était pas du code pour montrer la récursion aux
étudiants. C'était génial à maintenir et à faire évoluer (ajouter des
optimisations, par exemple). Il suffisait "d'ajouter au bon endroit un
"pattern" d'appel. Le nouveau pattern de transformation pouvait être
long à trouver (conception) mais il fallait 2 minutes pour l'ajouter
dans le code.
Au demeurant, peut-être que certains certains langages sont plus aptes
que d'autres à suivre au plus près la définition mathématique.



C'est pour cette raison que j'ai montré du code en Haskell qui est
réputé pour bien gérer la récursivité.
Avatar
candide
Wykaaa a écrit :

Alors je comprends que tu n'aime pas la récursivité.



Au contraire, j'adore pratiquer la récursivité et je peux passer des
heures à épurer un code récursif.

Je ne vois pas ce qui te laisse dire ça. Tout ce dont tu as parlé
précédemment n'a rien à voir avec le C me semble-t-il et je ne vois pas
en quoi ça m'aiderait à mieux coder en C.


Oui la suite de Fibonacci écrite de cette manière n'est pas optimisée
car les mêmes évaluations se font à plusieurs endroits.

ce qui une fois de plus montre qu'il y a une distance entre le concept
souvent simple voire trivial, et l'implémentation. Bref, ne confondons
pas récursivité de salon et récursivité de codeur.



Ce n'est pas à moi que tu vas tenir ce discours. J'ai trempé pendant 10
ans dans les compilateurs et il y avait de la récursion à tous les
étages...



La question n'était pas là, tu m'as dit qu'un algorithme récursif devait
suivre "au plus près" la définition mathématique. Mon exemple ci-dessus
prouve le contraire et d'autres exemples pourraient montrer que la
récursivité est plus compliquée à comprendre que ce que dit Richard
Delorme.
Avatar
bpascal123
On 29 nov, 12:55, Benoit Izac wrote:
Bonjour,

le 29/11/2009 à 11:07, -ed- a écrit dan s
le message
:

>> loin dans la programmation, je veux comprendre ce qui se passe dans la
>> machine tant que c'est à "porter de mains".

> Tu ne conduis une voiture que si tu es un expert en sciences
> mécanique ?

L'analogie est mauvaise. La programmation, c'est plutôt la fabrication
de la voiture, l'utilisation du programme étant la conduite.



Je suis d'accord.

Je veux fabriquer une voiture. Dans tous les cas il me faut des plans
(algorithmes) et c'est indépendant du choix que je vais faire pour sa
fabrication (langage de programmation). Ensuite, j'ai le choix :

  - je veux construire moi-même toutes les pièces (assembleurs)
  - je veux acheter toutes les matières premières et réaliser tou s les
    usinages, par contre j'utilise des vis du commerce (C)
  - je veux acheter des composants existants dans le commerce comme le
    moteur, la carrosserie et ne réaliser que l'assemblage (Python,
    Perl, etc.)



Je viens d'un pays très pauvre (dans les dix derniers selon le
classement...). Les 2 roues moto petites cylindrées avec et sans
embrayage importées sont fréquentes. Si je suis jeune dans ce pays qui
à ce jour offre peu d'avenir, je vais apprendre à réparer ces moteurs
et apprendre comment ça fonctionne. Maintenant dans certains cas, je
dois apprendre à forger pour fabriquer les pièces difficiles à trouve r
ou récupérer des pièces endommagés...déformées...

Ça reste une analogie, la réalité est un peu plus complexe quand au
choix du langage.

Ceci dit, pour quelqu'un qui n'est pas informaticien (comme moi) et
qui ne pratique pas la programmation de manière régulière, il ne fa ut
pas s'attendre à pouvoir programmer en C correctement avant plusieurs
années (pratiquement dix ans dans mon cas mais je suis une
feignasse ;-) ).



peut-être j'aurais besoin de 20 ans...sérieusement

Je pense qu'on apprends un langage de programmation comme une langue
étrangère : au début, on comprends rien et c'est bien normal. Il ne faut
surtout pas s'attarder sur tous les petits détails sinon on n'apprend
rien. Avec le temps, on commence à comprendre le sens des phrases mêm e
si on ne comprend pas chaque mot. Avec la pratique, on acquière des
automatismes qui permettent de ne plus réfléchir à certaines choses et
donc de pouvoir se concentrer sur d'autre dont on ignorait l'existence.



Progress is a small process*

Pour une personne qui apprend seule, parler couramment, avoir l'accent
et pouvoir faire du littéraire, c'est beaucoup, beaucoup de travail (je
veux pas décourager mais je pense même que c'est quasiment impossible ).
Si l'on veux vraiment avoir une maîtrise du langage, la seule solution
c'est d'aller dans un pays où les gens parlent cette langue (milieu
professionnel ou participer au développement d'une application réelle ).



Un ordinateur est déjà un outil formidable pour apprendre
l'informatique et c'est accessible.
Mais tu as raison, pour parler avec l'accent et les expressions
locales, il n'y a pas d'autres solutions que d'aller pendant une
longue période dans le pays et . Pour faire du littéraire, c'est une
autre question.

Mes deux cents.


*50 cents

Benoit Izac



Merci