OVH Cloud OVH Cloud

parcourir une structure

65 réponses
Avatar
Julien SCORDIA
Bonjour à tous,

J'ai une structure coucou comprenant des pointeurs. Dans certaines
conditions je cherche à allouer ou à libérer ces pointeurs. Je peux tout
faire à la main sans problème. Mais étant donné qu'un plus ou moins grand
nombre de ces pointeurs existe réellement dans la structure (du fait de
constantes préprocesseurs), j'aimerais ne pas avoir à changer mes fonctions
d'allocation et de libération de cette structure à chaque fois que je
rajoute un pointeur dans la structure ou que je change une constante
préprocesseur (par exemple).
C'est pourquoi j'aimerais pouvoir parcourir les divers pointeurs présents
dans la structure sans avoir à me soucier de leur nom, avec une sorte
d'itérateur. L'idée serait alors de tester le type du pointeur en cours, et
d'en déduire la bonne méthode d'allocation ou libération.
Comment peut-on réaliser cela en C?

Par ailleurs, je travaille sur un projet C depuis un bon moment, le
programme commence à être relativement conséquent (tout est relatif, c'est
un programme d'informatique scientifique: le nombre de lignes en ferait
sans doute sourire plus d'un sur ce forum). Un bon nombre de constantes
préprocesseurs sont bien pratiques (imbrication parfois de trois #ifdef
avec des && et || préprocesseurs), mais peuvent parfois gêner la lisibilité
et le parcours du code (sans le fameux % de vim, cela serait vraiment
laborieux). Que pensent les programmeurs professionnels et expérimentés des
constantes préprocesseur?

Merci d'avance pour vos conseils avisés,

Julien
--
"Allez, Monsieur, allez, et la foi vous viendra." (D'Alembert).

10 réponses

3 4 5 6 7
Avatar
Julien SCORDIA
Jean-Marc Bourguet wrote:

À nouveau, ce n'est pas ton problème c'est la solution que


Voir un post que je viens d'envoyer à Harpo plus bas, où je détaille mon
problème comme tu me l'as demandé.

un programme séparé en fonction d'une configuration, une
utilisation plus masquante des macros dans le genre


Merci pour ta solution. Pour l'instant je ne sais si elle peut me convenir.
--
"Allez, Monsieur, allez, et la foi vous viendra." (D'Alembert).

Avatar
Harpo
Julien SCORDIA wrote:

Harpo wrote:

D'abord merci pour ta réponse et ta solution à base d'unions.

Soit c'est su au moment de la compilation du programme, auquel cas on
peut utiliser le préprocesseur, mais on peut se demander s'il ne vaut
pas mieux faire 3 programmes et des fonctions communes au 3
programmes.


C'est ce à quoi j'avais pensé par un moment.
Seulement, pour chacune des "maisons" correspondant à chacun des
programmes, les fonctions se seraient drôlement ressemblées.
Franchement, il aurait été très dur voire impossible de faire des
fonctions communes de manière à ne pas avoir de redondances.


Je pense qu'il pourrait peut-être être bon de réflêchir encore à cela.
Cela passe par l'analyse de ce qui est spécifique à un vehicule et de
ce qui est commun à chaque véhicule.
A moins d'avoir un projet trivial (on écrit, on utilise et on jette dans
la même matinée (mais dans ce cas C est généralement un mauvais
choix)), il est très rare que l'on perde du temps à réflèchir à ce
qu'on va faire.

Aujourd'hui, j'ai environ 40 "maisons" différentes.


Tu ne manque pas de choix pour passer tes vacances, j'espère qu'elles ne
sont pas toutes dans le même paté de maison.

Pour répondre à la
demande de Jean-Marc Bourguet (post récent plus haut dans ce thread),
voici mon problème.
En fait mes maisons sont des architectures de véhicules différentes,
avec on sans batterie, avec ou sans moteur électrique, hybride série,
parallèle, ou série-parallèle (par exemple, il y a bien d'autres
options). Le programme utilise la programmation dynamique (principe de
Bellman) pour optimiser la consommation de carburant sur un cycle
vitesse/temps connu par avance (c'est pourquoi j'ai tout intérêt à ce
que le programme soit optimisé, c'est gourmand en mémoire et en temps
de calcul). J'ai bien découpé mon programme en diverses fonctions.
Mais suivant l'architecture de véhicule considérée parmi les 40, des
différences apparaissent un peu partout, dont j'ai tenu compte avec
mes #if defined(...).
Dans un fichier parameters.h de mon projet, il y a le #define VEHICLE
23 (par exemple véhicule 23) qui permet de compiler le projet avec la
bonne architecture.

Avec ta solution à base d'unions ou les autres proposées, je trouve
que les choses ne sont pas forcément plus simples (peut-être un peu
plus lisible mais moins intuitif). Mais je me trompe peut-être.


Je ne sais pas si le fait de mettre des #if partout rend un programme
intuitif, mais je sais que moi ça me fatigue et que je déteste ça.

Ok ! Maintenant nous avons une meilleure vision des choses.
Sans vouloir te faire divulguer des projets industriels, une question
serait :
Tous ces tests sur le véhicule sont-ils dans des endroits du programme
où la question de performance est importante (dans des boucles
fortement imbriquées) ?
Je me base sur un oui à cette réponse pour ce qui suit.

Il faut dans ce cas privilégier ce qui est fait à la compile par rapport
à ce qui est dynamiquement fait à l'exécution. i.e. on n'en a rien à
foutre de passer 1 seconde à compiler un programme si son exécution
passe de 2 heures à 1 heure.
Nous aborderons plus en détail les problèmes liés à la vitesse
d'exécution d'un programme ultérieurement si vous le voulez bien. Mais
schématiquement ces problèmes tiennent principalement à 2 choses :
1 ) Une mauvaise conception, l'utilisation d'algorithmes un peu louches
ou mal implémentés. C'est le plus important mais je vous fais confiance
sur ce point.
2 ) Une programmation gourmande, l'utilisation d'instructions ou de
fonctions qui bouffent du CPU comme moi des cacahouètes à des endroits
où elles sont utilisées souvent. C'est beaucoup moins important car on
peut généralement corriger sans avoir tout à revoir.

Si nous nous basons sur une approche statique basée sur l'utilisation du
précompilateur qui demande son intervention à chaque fois qu'on tourne
le programme, cela n'empêche pas, *au contraire*, de veiller à ce qui
est spécifique (ici à un véhicule) soit séparé de ce qui ne l'est pas.

Dans notre cas, c'est l'algorithme qui est commun, mélanger ce qui est
algorithmique et les choses bassement matérielles comme des cylindrées
et des débits de gicleur etc. est disgracieux, fatiguant etc.


L'idée de mon post initial était donc de me débarrasser des #ifdef, là
où peut-être ce serait possible;


Les #ifdefs toussa ne sont pas vraiment sataniques de manière
intrinsèque, Je pense que dans le C on peut considérer qu'il y a 2
langages, le macro-langage du préprocesseur et le langage C proprement
dit qui travaille sur ce qui est passé par le préprocesseur.
Ces 2 honorables langages ont chacun leur syntaxe, c'est leur
intermixage qui rend les choses problématique au niveau de la
lisibilité pour 2 raisons principales :
1 ) cela allonge le code d'un algo, il tient sur 2 ou 3 pages d'écran au
lieu d'une, ce qui occasionne de la fatigue pour le comprendre.
2 ) Je sais plus, Ah oui ! leur syntaxe et leur sémantique différente
est obfuscant.
3 ) Y'a ka lire poiur voir !

Un algorithme implémenté par un ensemble de fonction donne toujours des
trucs comme ça :

int mon_algo ( int blurb, monStruct *blah )
{
int i, j ; // et pourquoi pas libellule ou papillon ?
blah0 ;
for ( i = blurb ; i > blah->i ; i-- )
{
blah1 ;
if ( )
{
blah2 ;
for ( )
{
}
}
else
{
}
}
return ( j ) ;
}

N'est-ce pas d'une limpidité qui nous émeut jusqu'au plus profond de
notre âme ? En plus, tout le monde a reconnu du premier coup d'oeil
l'algorithme de Ford.

Tu voudrais changer cela par :

int mon_algo ( int blurb, monStruct *blah )
{
int i, j ; // et pourquoi pas durite ou piston ?

#if defined( FERRARI )
blah0.1 ;
#elif defined( FORD )
blah0.2;
#elif defined( MOBYLETTE )
blah0.3;
#elif defined( SOLEX )
blah0.4;
#endif
c
for ( i = blurb ; i > blah->i ; i-- )
{

#if defined( FERRARI )
blah1.1;
#elif defined( FORD )
blah1.2;
#elif defined( MOBYLETTE )
blah1.3;
#elif defined( SOLEX )
blah1.4;
#endif
if ( )
{

#if defined( FERRARI )
blah2.1;
#elif defined( FORD )
blah2.2;
#elif defined( MOBYLETTE )
blah2.3;
#elif defined( SOLEX )
blah2.4;
#endif
for ( )
{
}
}
else
{
}
}
return ( j ) ;
}

?

Sache que je ne laisserai jamais se commettre une telle ignominie!

Du fait que ce soient 2 langages différents il peut être bon de ranger
autant que ce faire se peut l'utilisation de ces langages dans des
endroit différents.

Si dans ton code tu as des choses comme ça :


----------------- .h

#define FERRARI 1
#define LADA 2
#define MASSEY_FERGUSSON 3
#define PEUGEOT 4

#define VEHICLE MASSEY_FERGUSSON

---------------- .c


// get the ratio
#if ( VEHICLE == FERRARI )
ratio = x->motor_ratio + x->turbo_ratio ;

#elif ( VEHICLE == LADA || VEHICLE == MASSEY_FERGUSSON )
ratio = x->motor_ratio ;

#else
ratio = x->motor_ratio + ( x->motor_ratio / blurb_value ) ;

#endif

Cela encombre le .c alors que fonctionnellement on veut juste connaître
le ratio.

=======================
Au lieu de mettre tout cela dans le .c, ce qui gâche la lecture de
l'algo, on peut faire :

----------------- .h

#define FERRARI 1
#define LADA 2
#define MASSEY_FERGUSSON 3
#define PEUGEOT 4

#define VEHICLE MASSEY_FERGUSSON

// get_turbo_ratio( X )
// blah ...
#if ( VEHICLE == FERRARI )
#define get_turbo_ratio( X ) ( (X)->turbo_ratio )

#elif ( VEHICLE == LADA || VEHICLE == MASSEY_FERGUSSON )
#define get_turbo_ratio( X ) 0

#else
#define get_turbo_ratio( X ) ( (X)->motor_ratio / blurb_value )

#endif

// ----------------- .c

// get the ratio
ratio = x->motor_ratio + get_turbo_ratio( x ) ;

Amha, cela permet de séparer les problèmes. Si on n'est pas sensible à
l'esthétique, on peut aussi se dire qu'en cas de changement dans les
données relatives aux voitures, il n'y a qu'un source à modifier sans
avoir à modifier ce qui n'est pas en rapport direct avec cette modif,
comme l'implémentation d'algo.
Or chacun sait qu'éditer un programme est une cause quasi-nécessaire des
ajouts de bugs.

Note :
Si tu utilises GNU/Linux (merci de ne pas oublier 'GNU'), tu utilises
aussi probablement GCC, tu n'as pas besoin de modifier le source pour
mettre :
#define VEHICLE MASSEY_FERGUSSON
Cela peut être entré de la ligne de commande par l'option '-D'

--
Patrick http://patrick.davalan.free.fr/


Avatar
Boogabi
Maintenant, afin de voir un peu de code et m'en inspirer, je repose ma
question: auriez-vous un projet sous Linux à me conseiller, dont je
pourrais lire le code? Un projet où le même type de problème se poserait, à
savoir une structure "à géométrie variable" avec juste ce qu'il faut
suivant le choix de l'utilisateur.


Un programme spécialement sous Linux ? Ca sent les appels systèmes
(fork, wait, select, mkfifo, open, read, write, pipe, ftok, sigaction,
etc...)...

Je ne pense pas que ce soit un programme linux que tu ais besoin, mais
un programme C tout ce qu'il y a de plus classique (et portable).

Mais bon, dans les deux cas, je pense que des gars comme James Kanze en
ont toute une tripotée dipsonibles librement sur leur site (à moins que
ce soit C++ pour lui, je ne sais plus). cf leur signature.

Avatar
Julien SCORDIA
Harpo wrote:

C'est ce à quoi j'avais pensé par un moment.
Seulement, pour chacune des "maisons" correspondant à chacun des
programmes, les fonctions se seraient drôlement ressemblées.
Franchement, il aurait été très dur voire impossible de faire des
fonctions communes de manière à ne pas avoir de redondances.


Je pense qu'il pourrait peut-être être bon de réflêchir encore à cela.
Cela passe par l'analyse de ce qui est spécifique à un vehicule et de
ce qui est commun à chaque véhicule.


Dur dur... (voir plus bas)

A moins d'avoir un projet trivial (on écrit, on utilise et on jette dans
la même matinée (mais dans ce cas C est généralement un mauvais
choix)), il est très rare que l'on perde du temps à réflèchir à ce
qu'on va faire.


Tu veux dire que la plupart du temps, sur les gros projets, les programmeurs
commencent à coder tout de suite?

Tous ces tests sur le véhicule sont-ils dans des endroits du programme
où la question de performance est importante (dans des boucles
fortement imbriquées) ?


Pas forcément. La réponse est plutôt non.

Il faut dans ce cas privilégier ce qui est fait à la compile par rapport
à ce qui est dynamiquement fait à l'exécution. i.e. on n'en a rien à
foutre de passer 1 seconde à compiler un programme si son exécution
passe de 2 heures à 1 heure.


C'est sur cette réflexion que j'avais commencé à mettre des #ifdef partout.

[...]
ratio = x->motor_ratio + get_turbo_ratio( x ) ;

Amha, cela permet de séparer les problèmes. Si on n'est pas sensible à
l'esthétique, on peut aussi se dire qu'en cas de changement dans les
données relatives aux voitures, il n'y a qu'un source à modifier sans
avoir à modifier ce qui n'est pas en rapport direct avec cette modif,
comme l'implémentation d'algo.


Je suis d'accord, mais cela peut rendre le .h compliqué dans mon cas (voir
plus bas).

Note :
Si tu utilises GNU/Linux (merci de ne pas oublier 'GNU'), tu utilises
aussi probablement GCC, tu n'as pas besoin de modifier le source pour
mettre :
#define VEHICLE MASSEY_FERGUSSON
Cela peut être entré de la ligne de commande par l'option '-D'
Oui, je suis bien sous GNU/Linux ;-) Merci pour l'info concernant gcc.

Jusqu'à maintenant je faisais un sed -i "s///" sur le fichier parameters.h.

Merci pour ta réponse. Je vois que quand je te parle de Bellman, tu penses à
l'algorithme de Ford, tu n'es pas né de la dernière pluie ;-)
Je comprends ta façon de voir: langage préprocesseur et langage C proprement
dit: deux langages différents => pas dans le même fichier.
J'ai recensé (j'aurais peut-être dû le faire dès le début) les endroits où
les constantes préprocesseur interviennent:

1///////// Prototypes de fonction. Effectivement ce n'est pas très
lisible ;-)
D'après ce que vous m'avez tous indiqué, je vois trois solutions:
- passer tous les paramètres, et s'occuper d'un plus ou moins grand nombre
de ceux-ci dans la fonction (il va quand même falloir regarder à
l'intérieur dans quel cas on est pour appliquer telle ou telle
instruction).
- faire plusieurs programmes pour autant de véhicule avec une fonction
f_graph_return différente à chaque fois (dur pour la maintenance si un
changement général dans f_graph_solve apparaissait);
- définir le prototype avec une macro préprocesseur dans le .c, celle-ci
étant définie dans le .h (juste pour la lisibilité, mais il reste à rendre
aussi lisible le corps de la fonction).

static void f_graph_return(struct graph gr
,struct results * res
,struct transmission trans
#if !defined(ELECTRIC_GENERATOR)
,struct internal_combustion_engine ice
#endif /* !defined(ELECTRIC_GENERATOR) */
,struct homothetic * homo
#if (!defined(ICE_ALONE) && !defined(ICE_ALONE_PERFECT) || defined
DECOMPOSITION )
,struct electric_motor em
#endif /* if (!defined(ICE_ALONE) && !defined(ICE_ALONE_PERFECT) || defined
DECOMPOSITION ) */
#if !defined(ICE_ALONE) && !defined(ICE_ALONE_PERFECT)
,struct battery bat
#endif /* if !defined(ICE_ALONE) && !defined(ICE_ALONE_PERFECT) */
#if defined(SERIAL_HYBRID)
#if defined(ELECTRIC_GENERATOR)
,struct electric_generator gene
#else /* defined(ELECTRIC_GENERATOR) */
,struct electric_motor gene
,struct transmission transgene
#endif /* defined(ELECTRIC_GENERATOR) */
#endif /* defined(SERIAL_HYBRID) */
,struct two_pointers_and_sizes * soc
);

2////////// Dans le corps des fonctions.
Voici deux exemples extraits de la fonction évoquée précédemment. J'ai
enlevé les commentaires (qui sont en assez grande quantité...).
Pas évident de mettre cela dans le .h, surtout que si je dois faire comme ça
à chaque fois, le .h risque de devenir gros ;-)

-------- 1er extrait
#if (defined(ICE_ALONE) && defined(STOP_DO_NOTHING))

if ( ABOUT_EQUAL_2(initial_acc_p_meca,0)
&& ABOUT_EQUAL_2(initial_acc_p_elec,0) )
{
it2max=1;
}
#else

if ( ABOUT_EQUAL_2(initial_acc_p_meca,0) )
{
it2max=1;
}
#endif /* if (defined(ICE_ALONE) && defined(STOP_DO_NOTHING)) */
---------- fin du premier extrait.

---------- 2ième extrait
#ifdef STOP_POSSIBILITY
int it;

for(it=0;it<10;it++)
{
printf("nItération %i.n",it);

#endif /* ifdef STOP_POSSIBILITY */
--------- fin du 2ième extrait

(le for est refermé plus loin).

/****************************************************************************/
/* Structure de résultats pour le programme. */
/****************************************************************************/

3////////// La définition de mes structures
J'ai vu que je pouvais scinder une de mes structures en plusieurs autres
structures, comme je l'avais fait pour la structure véhicule.
Mais il n'empêche qu'il restera des #ifdef dans la structure ainsi que dans
les fonctions d'allocation et de libération.

À moins d'utiliser une solution où certains champs existent mais ne sont pas
utilisés, telle que celle que tu as proposé (avec des unions). (Hypothèse
pour la suite)
Alors pour la libération, on pourrait peut-être imaginer initialiser les
pointeurs à NULL (actuellement j'ai une fonction d'allocation qui réserve
la mémoire, et par exemple pour un vecteur de réels, les initialise à
0.0/0.0, i.e. NAN). Si le pointeur est resté à NULL, c'est qu'il n'a pas
été initialisé, on ne le libère pas. Ainsi on pourrait libérer
systématiquement tous les pointeurs dans une fonction par structure. Est-ce
une bonne solution?
En revanche pour l'allocation, soit on garde les constantes préprocesseur,
soit on utilise un enumération, mais ça revient à peu près au même je
trouve, sauf que avec les enum, il va falloir que le type de véhicule soit
passé en ligne de commande du programme (car je ne vais pas le changer en
dur dans les source .c ou .h à chaque fois que je change de véhicule, et
recompiler ensuite, autant utiliser une constante préprocesseur à ce
compte-là).

Julien
--
"Allez, Monsieur, allez, et la foi vous viendra." (D'Alembert).


Avatar
Stephane Legras-Decussy
Julien SCORDIA a écrit dans le message
: 42619a06$0$2723$

[snip.... pfffff....]

c'est le C le plus immonde que j'ai jamais vu...
même à 100 euro de l'heure, je debeugue pas un truc comme ça...

franchement, ton truc est simple :

tu as 23 types de vehicules :

switch(type)
{ case TYPE1:


// tout ton bazar sur le type 1
}
break;

..... jusqu'à.....

case TYPE23:


// tout ton bazar sur le type 2

}
break;
}

et voila, c'est plié...

si tu parviens à faire des fonctions génériques communes, tant mieux, sinon
tout est découplé, et alors, qu'est-ce que ça peut faire ?

les bazars dans chaque case se ressemblent beaucoup ? et alors ?
aucune importance, tu as du C lisible, clair et maintenable...
Avatar
Julien SCORDIA
Stephane Legras-Decussy wrote:

c'est le C le plus immonde que j'ai jamais vu...
même à 100 euro de l'heure, je debeugue pas un truc comme ça...


Mais *il n'y a pas de bug* (jusqu'à preuve du contraire)... 100% sane avec
Valgrind, et il fait exactement ce qu'on lui dit.
Il y a plein de fonctions, et déjà 3 personnes ont travaillé dessus, preuve
qu'on arrive quand même à le lire.
Mais oui, il y a plein de constantes préprocesseur partout. Et c'est
difficilement lisible. C'est pourquoi je pose cette question sur le forum.
Cela dit, avec le % de vim, on se ballade très bien dans ce code.

les bazars dans chaque case se ressemblent beaucoup ? et alors ?
aucune importance, tu as du C lisible, clair et maintenable...


Si j'obtiens du code qui se répète d'un case à l'autre, cela veut dire que
si je dois faire une modif (algorithmique, j'entends) dans un case, je dois
le faire dans tous les autres. Cela n'est-il pas source d'erreur? Ça ne me
paraît pas génial pour la maintenance...
En plus, quand y'a 500 lignes dans chaque case, c'est un peu lourd...

Je pense qu'il serait possible de faire sans constantes préprocesseur, mais
alors il faudrait encore découper plein de fonctions. Ce serait un
découpage pas évident, et qui ne pouvait être fait que difficilement lors
de la conception (si, réellement).
J'arrive à ce genre de problèmes du fait de tous ces cas qui peuvent
apparaître.
Je n'ai pas encore vu de solution miracle.

Julien
--
"Allez, Monsieur, allez, et la foi vous viendra." (D'Alembert).

Avatar
Stephane Legras-Decussy
Julien SCORDIA a écrit dans le message
: 42668c93$0$14387$
Mais *il n'y a pas de bug* (jusqu'à preuve du contraire)... 100% sane avec
Valgrind, et il fait exactement ce qu'on lui dit.


c'est comme un flingue... il est *toujours* chargé même quant il
ne l'est pas...


Si j'obtiens du code qui se répète d'un case à l'autre, cela veut dire que
si je dois faire une modif (algorithmique, j'entends) dans un case, je
dois

le faire dans tous les autres. Cela n'est-il pas source d'erreur? Ça ne me
paraît pas génial pour la maintenance...


si ce cas de figure arrive, c'est que tu as loupé l'occasion de faire une
bonne fonction...

En plus, quand y'a 500 lignes dans chaque case, c'est un peu lourd...


une fonction de 500 lignes qui déroule tranquillement de bas en haut
vaut mille fois mieux que ces bidouilles de ifdef...


Je pense qu'il serait possible de faire sans constantes préprocesseur,
mais

alors il faudrait encore découper plein de fonctions.


OUI ! qu'est-ce qui te fait peur ? une bonne fonction fait 50 lignes
et fait une unique chose clairement definie....

il peut y avoir 500 fonctions comme ça, c'est limpide à lire et à
comprendre...

Ce serait un
découpage pas évident, et qui ne pouvait être fait que difficilement lors
de la conception (si, réellement).


c'est étonnant comme tu as un problème de conception et de vision globale
de ton projet...

tu dois avoir une très grosse habitude de pondre du C au fil de l'eau...
je sais, ça pique les doigts pour un programmeur mais pour ton projet,
il faut passer une semaine avec juste un papier et un crayon...

et tu ne dessineras que des rectangles et des flèches...

Avatar
Julien SCORDIA
Stephane Legras-Decussy wrote:

c'est étonnant comme tu as un problème de conception et de vision globale
de ton projet...

tu dois avoir une très grosse habitude de pondre du C au fil de l'eau...
je sais, ça pique les doigts pour un programmeur mais pour ton projet,
il faut passer une semaine avec juste un papier et un crayon...

et tu ne dessineras que des rectangles et des flèches...


Oui, j'en suis maintenant convaincu après ce thread, il faudrait fabriquer
tout un tas de fonctions supplémentaires.
Mais à mon humble avis, la tâche n'est pas facile, car le programme est
assez complexe d'un point de vue algorithmique, et du fait de la
multiplicité des cas de figure (les véhicules).
Faut voir l'historique: au début j'ai commencé en Matlab (avec lequel plein
de gens programment sale, tu sais les gens qui sortent d'école d'ing
généraliste et qui mettent sur leur CV: C, C++, etc. alors que pour
certains ils ne savent même pas ce qu'est un pointeur, j'en ai déjà vu
plusieurs. Moi je suis sorti d'école sans savoir ce qu'est un pointeur,
mais au moins je ne mettais pas ce genre de choses sur mon CV). Ça prenait
des heures de calcul dès les premières exécutions. J'ai donc décidé de
coder tout ça en C, en apprenant sur mon temps libre, on peut le dire, avec
l'aide d'internet et des compétences telles que les vôtres. Le résultat a
été à la hauteur des espérances (plein de résultats très intéressants en
des temps décents), mais il est vrai que faudrait remettre tout ça à plat,
pour la forme et l'évolutivité du code.

Il faudrait une refonte qui prendrait à mon avis au moins un mois à 100%.
Comme ce programme est réservé à un usage en interne et que mon chef demande
mille choses à la fois, avec le moins de temps passé à programmer possible:
"faut aller au plus vite", vue des choses que je ne partage pas (d'où ce
post), cela ne se fera pas d'ici un bon bout de temps. Quoi qu'il en soit,
j'ai appris tellement de choses lors de la conception de ce programme et ce
qu'il y a autour (shell bash, sed, awk, etc. encapsulant le programme pour
exécution batch), je ne regrette pas du tout cette première expérience de
programmation relativement "sérieuse" (et qui je le rappelle a permis
d'obtenir tout un tas de résultat).

En tout cas, merci pour vos réponses à tous.

Julien
--
"Allez, Monsieur, allez, et la foi vous viendra." (D'Alembert).

Avatar
Harpo
Julien SCORDIA wrote:


A moins d'avoir un projet trivial (on écrit, on utilise et on jette
dans la même matinée (mais dans ce cas C est généralement un mauvais
choix)), il est très rare que l'on perde du temps à réflèchir à ce
qu'on va faire.


Tu veux dire que la plupart du temps, sur les gros projets, les
programmeurs commencent à coder tout de suite?


Non, enfin j'espère.
Je voulais dire que le temps passé à réfléchir n'est pas du temps perdu,
cela fait au contraire souvent gagner du temps par la suite.
Mais se jetter sur le code n'est pas ce qu'il y a de pire, une fonction
peut être récupérable comme brique d'un projet et si elle ne l'est pas
tant pis, elle libérera de la place sur le disque. Cela permet de
s'occuper les doigts pendant qu'on réfléchit en background.
Ce qui est bien pire, c'est de se fixer sur une architecture logicielle
trop vite, avant d'avoir évalué toutes les possibilités même celles qui
peuvent sembler les plus farfelues et d'avoir critiqué celles qui nous
paraissent les plus éligibles.
J'ai remarqué qu'il n'était pas extrêmement rare que les gens appliquent
des idées préconçues à des problèmes auxquels elles ne correspondent
pas tout à fait. Le fait même de penser trop tôt à des solutions
techniques limite le champ des solutions possibles.


Tous ces tests sur le véhicule sont-ils dans des endroits du
programme où la question de performance est importante (dans des
boucles fortement imbriquées) ?


Pas forcément. La réponse est plutôt non.


Rhâzut ! j'ai fait une mauvaise hypothèse !

Dans ce cas, il est *peut-être* préférable de tester dynamiquement les
différentes caractéristiques des véhicules.
Par exemple en les mettant sur une espèce de database externe aux
programmes.

L'intérêt de tests faits par le préprocesseur aurait pu être de diviser
le temps en générant un programme adapté à chaque fois.
Si cela ne fait pas gagner de temps de manière significative, je ne vois
guère de raison d'utiliser un tel design. Les macros peuvent très
utiles pour rendre un code plus lisibles, mais quand elles ont l'effet
inverse il faut qu'elles puissent être justifiées d'une autre manière.

La manière la plus classique est de paramêtrer le programme en lui
indiquant sur quoi il doit travailler et la manière de retrouver les
données correspondante. Il y a un grand choix de moyens. Dans le cas
nous concernant, l'exemple suivant pourrait être une solution :
Le programme serait appelé comme cà :
$ monprog --vehicle ferrari.xml

ferrari.xml étant un fichier (ici en xml) contenant toutes les données
pertinentes relatives au véhicule (débit du carburateur et tout ce
qu'on veut). C'est un fichier du style fichier de config.

Le programme remplit ensuite une structure dans laquelle peuvent
apparaître tous les parametres possibles et un certain nombre de
variable booléennes indiquant quelles fonctionnalités le véhicule
implémente etc. Cela peut faire une grosse structure mais même si elle
fait 1 kb ou même 50 kb voire 500kb, ce n'est pas ça qui aura un grand
effet sur les performances du programme.
Le programme teste ensuite au moment opportun le contenu de la structure
pour fournir aux algos les valeurs dont il a besoin.

Pourquoi un fichier en xml ?
1 - parce que c'est un standard et que l'on trouve des logiciels qui le
traitent un peu partout, Il serait étonnant qu'il n'y ait pas en C un
parser de xml, cela évite de faire le design d'un langage et d'écrire
un parser. Mais si on veut se faire plaisir (??) on peut aussi utiliser
bison et flex.
2 - ce n'est pas si verbeux que ça, dans le fond ...

Ce n'est qu'un exemple, l'important me semble être de donner à 1
programme les données qu'il a besoin et non de génèrer un programme
pour une utilisation particulière. Le programme n'a pas à connaître des
notions telles que 'ferrari' ou 'ford' et des modèles qui changent tout
le temps.


[...]
ratio = x->motor_ratio + get_turbo_ratio( x ) ;

(...)


Je suis d'accord, mais cela peut rendre le .h compliqué dans mon cas
(voir plus bas).


L'important est de séparer les problèmes, ceux qui tiennent à l'algo et
ceux qui tiennent à son utilisation, et séparer les langages, celui du
préprocesseur et le C proprement dit.

Cela entre dans une stratégie de découplage. Les problèmes doivent être
traité au niveau où ils doivent l'être.

Le problème reste le même si on utilise quelque chose de plus dynamique
comme dans l'ébauche de solution présentée plus haut. il me semble
incongru d'importuner Bellman avec des histoires de carburateur et d'en
truffer son algo.
Je ne sais pas pourquoi tu as besoin des données dans ton algo, mettons
que ce soit pour valuer les arcs (si ce n'est pas ça, l'exemple peut
peut-être quand même s'appliquer), il appelle une fonction
value_arc(theStruct) qui lui renvoit la bonne valeur.
Si un programme n'est pas structuré en couches fonctionnelles, il
devient de plus en plus ingérable à mesure qu'on lui rajoute des
fonctionnalités.

Cela peut être entré de la ligne de commande par l'option '-D'


Oui, je suis bien sous GNU/Linux ;-) Merci pour l'info concernant gcc.
Jusqu'à maintenant je faisais un sed -i "s///" sur le fichier
parameters.h.


Ca marche, mais cela a le désavantage de modifier le .h, notamment son
time-stamp.
De plus, si on veut faire tourner parallèlement 2 instances différentes
du programme cela complique un peu plus.

J'ai recensé (j'aurais peut-être dû le faire dès le début) les
endroits où les constantes préprocesseur interviennent:


Ce n'est peut-être pas la bonne manière de départ de poser la question,
ce serait plutôt "à quel moment les caractéristiques spécifiques aux
véhicule interviennent ?".
La manière dont tu formules la question sous-entend un choix
d'implémentation?


1///////// Prototypes de fonction. Effectivement ce n'est pas très
lisible ;-)
D'après ce que vous m'avez tous indiqué, je vois trois solutions:
- passer tous les paramètres, et s'occuper d'un plus ou moins grand
nombre de ceux-ci dans la fonction (il va quand même falloir regarder
à l'intérieur dans quel cas on est pour appliquer telle ou telle
instruction).
- faire plusieurs programmes pour autant de véhicule avec une fonction
f_graph_return différente à chaque fois (dur pour la maintenance si un
changement général dans f_graph_solve apparaissait);
- définir le prototype avec une macro préprocesseur dans le .c,
celle-ci étant définie dans le .h (juste pour la lisibilité, mais il
reste à rendre aussi lisible le corps de la fonction).

static void f_graph_return(struct graph gr
,struct results * res
,struct transmission trans
#if !defined(ELECTRIC_GENERATOR)
,struct internal_combustion_engine ice
#endif /* !defined(ELECTRIC_GENERATOR) */


(snip une 20aine de lignes comme ça)
Lorsque le nombre de paramêtres augmentent, il peut être bon de passer
une structure contenant ces paramêtres.


2////////// Dans le corps des fonctions.
Voici deux exemples extraits de la fonction évoquée précédemment. J'ai
enlevé les commentaires (qui sont en assez grande quantité...).
Pas évident de mettre cela dans le .h, surtout que si je dois faire
comme ça à chaque fois, le .h risque de devenir gros ;-)

-------- 1er extrait
#if (defined(ICE_ALONE) && defined(STOP_DO_NOTHING))

if ( ABOUT_EQUAL_2(initial_acc_p_meca,0)
&& ABOUT_EQUAL_2(initial_acc_p_elec,0) )
{
it2max=1;
}
#else

if ( ABOUT_EQUAL_2(initial_acc_p_meca,0) )
{
it2max=1;
}
#endif /* if (defined(ICE_ALONE) && defined(STOP_DO_NOTHING)) */
---------- fin du premier extrait.


Là cela peut se faire par des macros, ou une fonction ( éventuellement
inline ) get_it2max( ), que ABOUT_EQUAL_2( x ) soit une macro du
préproc ou une fonction qui teste une variable (ou les deux).
A ce sujet, je pense qu'il n'est pas recommandable de mettre des
Majuscules à ABOUT_EQUAL_2 du fait que le fait que ce soit une macro ou
une fonction est un choix d'implémentation qui n'interfère pas avec la
fonction qui l'utilise, fonctionnellement il s'agit d'une fonction.


---------- 2ième extrait
#ifdef STOP_POSSIBILITY
int it;

for(it=0;it<10;it++)
{
printf("nItération %i.n",it);

#endif /* ifdef STOP_POSSIBILITY */
--------- fin du 2ième extrait

(le for est refermé plus loin).


C'est moins évident ...
On peut se demander s'il ne vaut pas mieux découper le code autrement.

(...)


Alors pour la libération, on pourrait peut-être imaginer initialiser
les pointeurs à NULL (actuellement j'ai une fonction d'allocation qui
réserve la mémoire, et par exemple pour un vecteur de réels, les
initialise à 0.0/0.0, i.e. NAN). Si le pointeur est resté à NULL,
c'est qu'il n'a pas été initialisé, on ne le libère pas. Ainsi on
pourrait libérer systématiquement tous les pointeurs dans une fonction
par structure. Est-ce une bonne solution?


Il me semble.
J'ai fait un petit code à ce sujet, pas testé, juste vu s'il ne fait pas
de segfault.

======================================= #include <stdio.h>
#include <stdlib.h>

// ----------------- .h
// libère la mémoire et met l'argument à NULL
// (une fonction inline serait mieux.)
#define free_null( X ) { void **P = &X ;free( *P ) ; *P = NULL ; }

typedef enum
{
// Le véhicule comprendra un moteur, un alternateur et un turbo
// => num = 3
moteur, alternateur, turbo, num
} Symbs ;

typedef struct
{
void *pointers[ num ] ; // pointeurs vers des bouts de mémoire
// le reste de la structure ici.
} TheStruct ;

// -----------------

// cette fonction peut (devrait ?) être dans un source séparé
void
free_all( void * pointers[], unsigned int num )
{
// libère les éléments alloués dans un tableau de pointeurs
unsigned int i ;
for ( i = num ; --i ; )
{
if ( pointers[ i ] != NULL )
{
free_null ( pointers[ i ] ) ;
pointers[ i ] = NULL ;
}
}
}

// -----------------
int
main( void )
{
TheStruct *pStruct ;
int i ;

pStruct = calloc( sizeof( *pStruct ), 1 ) ; // assume NULL = 0
if ( pStruct == NULL )
{
fprintf( stderr, "Cannot allocate the struct.n") ;
abort( ) ;
}

// Quelques allocations et libérations
// rajouter un test après les malloc

// il faut s'y reprendre à plusieurs fois avant de démarrer un
// moteur
// remplacer 512 etc. par des sizeof( )
for ( i = 10 ; i-- ; )
{
pStruct->pointers[ moteur ] = malloc ( 1024 ) ;
free_null( pStruct->pointers[ moteur ] ) ;
}
pStruct->pointers[ moteur ] = malloc ( 1024 ) ;
pStruct->pointers[ alternateur ] = malloc ( 2048 ) ;
free_null( pStruct->pointers[ alternateur ] ) ;
pStruct->pointers[ turbo ] = malloc ( 512 ) ;
// ici seul moteur et turbo sont alloués

// libère la mémoire non libérée.
free_all( pStruct->pointers, num ) ;

// quelques traitements

// libère la mémoire non libérée par les traitements.
free_all( pStruct->pointers, num ) ;

exit( EXIT_SUCCESS ) ;
}

=======================================
En revanche pour l'allocation, soit on garde les constantes
préprocesseur, soit on utilise un enumération, mais ça revient à peu
près au même je trouve, sauf que avec les enum, il va falloir que le
type de véhicule soit passé en ligne de commande du programme (car je
ne vais pas le changer en dur dans les source .c ou .h à chaque fois
que je change de véhicule, et recompiler ensuite, autant utiliser une
constante préprocesseur à ce compte-là).


Soit le programme exécutable est statique dans le sens où il ne peut
traiter qu'un type de véhicle auquel cas il faut indiquer le véhicule à
la compile, soit il peut traiter plusieurs types de véhicules auquel
cas il faut donner ce type à l'exécution..
C'est très chiant, mais obtenir les arguments de la ligne de commande
n'est pas d'une complexité insurmontable, voir le manuel de getopt,
getoptlong.

--
Patrick http://patrick.davalan.free.fr/


Avatar
Harpo
Julien SCORDIA wrote:

Mais à mon humble avis, la tâche n'est pas facile, car le programme
est assez complexe d'un point de vue algorithmique, et du fait de la
multiplicité des cas de figure (les véhicules).


Il y a 2 complexités, les placer au même endroit les multiplie.

Le résultat a été à la hauteur des espérances
(plein de résultats très intéressants en des temps décents), mais il
est vrai que faudrait remettre tout ça à plat, pour la forme et
l'évolutivité du code.


Il faut pouvoir réflèchir au problème en faisant abstraction de ce qui
existe. Ensuite voir ce qu'on peut faire des solutions que l'on a
imaginé et du code existant.


Il faudrait une refonte qui prendrait à mon avis au moins un mois à
100%. Comme ce programme est réservé à un usage en interne et que mon
chef demande mille choses à la fois, avec le moins de temps passé à
programmer possible: "faut aller au plus vite", vue des choses que je
ne partage pas (d'où ce post), cela ne se fera pas d'ici un bon bout
de temps.


Passer le moins de temps à programmer est une bonne idée, une bonne
partie des erreurs de programmation viennent du fait que des gens ont
programmé.

Hugh !

--
Patrick http://patrick.free.fr/

3 4 5 6 7