OVH Cloud OVH Cloud

parametres simples en const ?

70 réponses
Avatar
JBB
Bonjour

Lorsque je crée une fonction qui prend un entier en paramètre je fais quelque chose du genre:

int Double( int x)
{
return 2*x;
}

Je me demande s'il il n'est pas plus sage de faire
int Double( const int x)
{
return 2*x;
}
dans la mesure ou je n'ai pas l'intention de modifier x dans le corps.

Cela permettrait d'eviter certaines erreurs (comme affecter x par erreur), par exemple :
int Rapport(const int a,const int b)
{
bool bDivisionParZero = ( b = 0);
if (bDivisionParZero )
{
//exception
}
else
{
return a / b;
}
}
Si b est declare const ce code ne compile pas.
ou alors
int rapport(const int nombreElements,Truc * Tableau)
{
for (;nombreElements >0; nombreElements--)
{
...
}

//renvoyer le nombre d'elements traites
return nombreElements;
}

Est ce une pratique courrante?

10 réponses

1 2 3 4 5
Avatar
Loïc Joly
Bonjour à tous.

Je vais peut-être poser une question bête, mais voici :

Je vois bien l'intérêt de déclarer const un tel argument
(programmation défensive = préférer les erreurs de compilations aux
erreurs à l'exécution, dont chacun sait qu'elles sont toujours lieu
en présence de M. Demesmaeker).

A l'inverse, je ne comprends pas en quoi cette déclaration const peut
gêner. Autrement dit : qu'est-ce qui justifie de telles réticences
face à ce genre de déclarations ?


Le fait qu'en interne de la fonction, on décide ne ne pas modifier un
argument qui a été passé par copie est un détail d'implémentation, qui
n'a donc pas sa place dans la déclaration de la fonction, uniquement
dans sa définition.

Maintenant, il est vrai que même dans la définition j'en met rarement.
Question d'habitude, probablement.

--
Loïc

Avatar
Alexandre
(et se coder directement (x << 1))
Sur ?



j'ai du mal à imaginer ce qui pourrait en faire douter.

ici le type retour a la même taille que l'argument donc le meilleur codage
(si on se soucie des perfs); une fonction long Double(int) devrait elle
être codée { return 2L * x; } pour éviter l'overflow (si "int" n'a pas la
taille de "long").

oui, mais x<<1 ne se lit pas "facilement" comme le double de x (alors que

2*x...)
niveau performances, rien ne te permet (comme le post de james kanze te le
dit) que x<<1 est plus rapide que 2*x. Sur un 8086, certes ;-) mais sur un
cpu moderne...
pour faire x<<1 il faudra un cycle d'horloge
pour faire 2*x également, mais si plusieurs unités entières travaillent en
même temps (les cpu modernes sont souvent superscalaires) on peut, en valeur
moyenne, descendre sous la durée d'un microcycle...

Et encore, l'optimisation devrait être faite par le compilateur plutôt que
par le programmeur ici (sauf si tu programmes pour un microcontroleur avec
un compilo C qui n'optimise pas trop mais je pense qu'on s'éloigne du
sujet)... Sur mon système (Athlon XP) avec mon compilo (borland bcc55) le
code 2*x est compilé avec l'instruction SHL (décalage à gauche) de même que
x<<1... Le compilo a décidé (et je pense que sur cette plate-forme précise
il n'a pas tort) de l'optimisation...
Quoi qu'il en soit, la différence de vitesse AMA ne vaut pas de perdre la
lisibilité du code



Avatar
Sylvain
Alexandre wrote on 04/03/2006 16:52:

oui, mais x<<1 ne se lit pas "facilement" comme le double de x (alors que

2*x...)


acte 1: il y a peu, on a pu lire ici des avis très "campés" sur le bon
usage des opérateurs "<<" et ">>", notamment certains trouvaient
choquant de les utiliser comme injecteurs / extracteurs puisqu'ils ne
seraient que des opérateurs mathématiques de décalage.

acte 2: "x << 1" serait /moins lisible/ que "2 * x"

ce bavardage (ou avis très personnel) m'amuse un peu, si un codeur C ne
sait pas lire "x << n" je lui conseillerais humblement de commencer à
s'initier au langage qu'il pense utiliser ou de passer à autre chose.

niveau performances, rien ne te permet (comme le post de james kanze te le
dit) que x<<1 est plus rapide que 2*x. Sur un 8086, certes ;-) mais sur un
cpu moderne...


du post de James j'ai lu qu'il avait "une fois du mesurer" mais il a
oublié de présenter / commenter le résultat de sa mesure; donc "rien ne
me permet de penser" que son expérience contredise ce point.

de ma propre expérience, je mesure et constate un gain; et ce sur petits
procs (8086) comme sur Sparc 2 avec Sun CC.

pour faire x<<1 il faudra un cycle d'horloge


sur quel proc ? pour un 8086 il en faut 2 ;)

pour faire 2*x également


non, en aucun cas, ou plutôt même si un MUL (au moins 70 cycles sur
8086) demandait le même nombre de cycles, l'opération complète demande
ici le chargement de 2 opérandes (1 registre + 1 reg/mem) (vs valeur
immédiate pour un SHL).

mais si plusieurs unités entières travaillent en même temps
(les cpu modernes sont souvent superscalaires) on peut, en valeur
moyenne, descendre sous la durée d'un microcycle...


d'accord sur la forme (l'architecture des CPU moderne aide à obtenir de
bonnes perfs) mais pas sur le fond (le bons sens n'est pas réservé aux
ingénieurs concevant les CPU et un développeur a le droit de savoir lire
et utiliser "x << n").

Et encore, l'optimisation devrait être faite par le compilateur plutôt que
par le programmeur ici (sauf si tu programmes pour un microcontroleur avec
un compilo C qui n'optimise pas trop mais je pense qu'on s'éloigne du
sujet)...


le sujet ne fixait pas un CPU / compilateur particulier.

GCC avant BCC savait remplacer les "mauvaises" multiplications par des
shifts, mais tous ne le font pas; utiliser ces shifts rend de plus le
code déterministe (il ne depend pas de la seule qualité du pré-proc C)

Sur mon système (Athlon XP) avec mon compilo (borland bcc55) le
code 2*x est compilé avec l'instruction SHL (décalage à gauche) de même que
x<<1... Le compilo a décidé (et je pense que sur cette plate-forme précise
il n'a pas tort) de l'optimisation...


et comment code-t-il (x * 80) ? ((x << 4) + (x << 6))
ou encore (x * 127) ? génére-t-il: (x << 7) - x ? (pas "- 1" !)

Quoi qu'il en soit, la différence de vitesse AMA ne vaut pas de perdre la
lisibilité du code


je dirais plutôt la qualité du compilo requiert ou non de coder soi-même
ces optimisations; si l'usage d'opérateurs shift apparait comme
"obfuscant" ce sont les aptitudes du programmeur qu'il faut questionner
plus d'une prétendue lisibilité.

Sylvain.


Avatar
Fabien LE LEZ
On Sat, 04 Mar 2006 12:34:06 +0100, James Kanze :

(et se coder directement (x << 1))




Tu as de mesures à l'appui, j'espère, si tu parles de la
performance. Une application où une telle obfuscation faisait la
différence entre une performance acceptable et une performance
non-acceptable.


Et surtout, il y a de fortes chances pour que le compilo génère le
même code pour "x*2" et "x<<1"...




Avatar
Fabien LE LEZ
On Sat, 04 Mar 2006 18:00:49 +0100, Sylvain :

acte 1: il y a peu, on a pu lire ici des avis très "campés" sur le bon
usage des opérateurs "<<" et ">>", notamment certains trouvaient
choquant de les utiliser comme injecteurs / extracteurs puisqu'ils ne
seraient que des opérateurs mathématiques de décalage.


On peut maintenant trouver choquant d'utiliser, pour bricoler des
bits, des opérateurs dédiés aux iostreams.

acte 2: "x << 1" serait /moins lisible/ que "2 * x"

ce bavardage (ou avis très personnel) m'amuse un peu, si un codeur C ne
sait pas lire "x << n" je lui conseillerais humblement de commencer à
s'initier au langage qu'il pense utiliser ou de passer à autre chose.


Euh... J'ai pas suivi, là. Ne sommes-nous pas sur un forum consacré au
C++ ?

Et franchement, si un codeur C++ utilise ce genre de hack, c'est à lui
que je conseillerais d'aller voir ailleurs.

Pour le C, je ne sais pas, je ne connais pas ce langage.

je dirais plutôt la qualité du compilo requiert ou non de coder soi-même
ces optimisations


Non. Toute optimisation est commandée par le profiler, une fois qu'on
s'aperçoit que le programme réel est effectivement trop lent.

Avatar
Alexandre
bonjour,

acte 2: "x << 1" serait /moins lisible/ que "2 * x"

ce bavardage (ou avis très personnel) m'amuse un peu, si un codeur C ne
sait pas lire "x << n" je lui conseillerais humblement de commencer à
s'initier au langage qu'il pense utiliser ou de passer à autre chose.


pas d'accord. D'abord on ne parle que de codeurs C++ ici ;-)
ensuite x<<n peut très bien être "caché" avec des expressions plus ou moins
complexes, etc...
Je n'ai pas dit qu'il était illisible d'écrire x<<1 à la place de 2*x, mais
*moins lisible*


pour faire x<<1 il faudra un cycle d'horloge


sur quel proc ? pour un 8086 il en faut 2 ;)

pour faire 2*x également


non, en aucun cas, ou plutôt même si un MUL (au moins 70 cycles sur 8086)
demandait le même nombre de cycles, l'opération complète demande ici le
chargement de 2 opérandes (1 registre + 1 reg/mem) (vs valeur immédiate
pour un SHL).


1 cycle pour le calcul, effectivment il faut charger les opérandes

d'accord sur la forme (l'architecture des CPU moderne aide à obtenir de
bonnes perfs) mais pas sur le fond (le bons sens n'est pas réservé aux
ingénieurs concevant les CPU et un développeur a le droit de savoir lire
et utiliser "x << n").


encore une fois, certes, mais le gain de perfs ne vaut pas la perte de
lisibilité dans la majorité des cas. Bien sur, pour des applis embarquées
par ex, pour du code bas niveau, aucun souci.

Sur mon système (Athlon XP) avec mon compilo (borland bcc55) le code 2*x
est compilé avec l'instruction SHL (décalage à gauche) de même que
x<<1... Le compilo a décidé (et je pense que sur cette plate-forme
précise il n'a pas tort) de l'optimisation...


et comment code-t-il (x * 80) ? ((x << 4) + (x << 6))
ou encore (x * 127) ? génére-t-il: (x << 7) - x ? (pas "- 1" !)



voici le code généré par le compilo borland pour l'expression (je donne
l'intégralité du code de test)
tu vois que l'optimisation n'est pas si mal

int main()
{
int x;
/*
push ebp
mov ebp,esp
add esp, -0x08
*/
std::cin>>x;
/* ... */
int y=x*80;
/*
mov edx, [ebp-0x04]
shl edx, 0x04
lea edx,[edx+edx*4]
mov [ebp-0x08,edx]
*/
std::cout<<y;
/* ... */
y = x*127;
/*
mov ecx, [ebp-0x04]
mov eax, ecx
shl ecx, 0x07
sub ecx,eax
mov [ebp-0x08], ecx
*/
std::cout<<y;
return 0;
}



je dirais plutôt la qualité du compilo requiert ou non de coder soi-même
ces optimisations; si l'usage d'opérateurs shift apparait comme
"obfuscant" ce sont les aptitudes du programmeur qu'il faut questionner
plus d'une prétendue lisibilité.


je persiste à penser que, dans le cas général, l'optimisation n'est pas
souhaitable si elle nuit à la lisibilité du code source.
Et les opérateurs << et >> ne sont pas des multiplieurs/diviseurs mais des
opérateurs de décalage. Faire des multiplications à l'aide de décalages de
bits pour gagner qq cycles d'horloge me parait, *dans le cas général* une
mauvaise idée. Bien sur, si la vitesse brute est *primordiale* est plus
importante que la lisibilité du code, alors pourquoi pas.


Avatar
Sylvain
Fabien LE LEZ wrote on 04/03/2006 20:38:

Euh... J'ai pas suivi, là. Ne sommes-nous pas sur un forum consacré au
C++ ?


la question posée initialement "une fonction [non membre] qui prend un
entier en paramètre" n'avait donc rien à faire ici ?? que ne nous l'avez
vous signifiez plus tôt, un "circulez" aurait été tout indiqué.

Et franchement, si un codeur C++ utilise ce genre de hack,


de quoi ? j'ai pas mon grammar pack sous la main.

Pour le C, je ne sais pas, je ne connais pas ce langage.


il n'est jamais trop tard pour commencer à apprendre.

Non. Toute optimisation est commandée par le profiler,


voui, voui, le code sort du generater, l'IHM du designer, et l'analyse
n'existe simplement plus.

une fois qu'on s'aperçoit que le
programme réel est effectivement trop lent.


ah "trop" uniquement, s'il n'est que "assez" lent tout va bien ?

Sylvain.

Avatar
James Kanze
Fabien LE LEZ wrote:
On Sat, 04 Mar 2006 12:34:06 +0100, James Kanze :


(et se coder directement (x << 1))






Tu as de mesures à l'appui, j'espère, si tu parles de la
performance. Une application où une telle obfuscation faisait
la différence entre une performance acceptable et une
performance non-acceptable.



Et surtout, il y a de fortes chances pour que le compilo
génère le même code pour "x*2" et "x<<1"...


En effet. Bien que, curieusement, dans le seul cas que j'ai
analysé (« 127 * x » contre « x << 7 - x », avec Sun CC), pour
je ne sais pas quelle raison, le compilateur générait une
instruction de plus avec le décalage -- tout en se servant
d'exactement les mêmes instructions pour l'opération même. (Le
Sparc n'a pas, ou au moin n'avait pas à l'époque, une
instruction de multiplication des entiers.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34





Avatar
James Kanze
Alexandre wrote:
(et se coder directement (x << 1))
Sur ?

j'ai du mal à imaginer ce qui pourrait en faire douter.




ici le type retour a la même taille que l'argument donc le
meilleur codage (si on se soucie des perfs); une fonction
long Double(int) devrait elle être codée { return 2L * x; }
pour éviter l'overflow (si "int" n'a pas la taille de
"long").



oui, mais x<<1 ne se lit pas "facilement" comme le double de x
(alors que 2*x...) niveau performances, rien ne te permet
(comme le post de james kanze te le dit) que x<<1 est plus
rapide que 2*x. Sur un 8086, certes ;-) mais sur un cpu
moderne...


Qu'importe le CPU. C'est au compilateur de se débrouiller pour
générer le code optimal. Et je n'en connais pas qui ne le font
pas dans ce cas-ci.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34




Avatar
James Kanze
Sylvain wrote:
Alexandre wrote on 04/03/2006 16:52:


oui, mais x<<1 ne se lit pas "facilement" comme le double de
x (alors que 2*x...)



acte 1: il y a peu, on a pu lire ici des avis très "campés"
sur le bon usage des opérateurs "<<" et ">>", notamment
certains trouvaient choquant de les utiliser comme injecteurs
/ extracteurs puisqu'ils ne seraient que des opérateurs
mathématiques de décalage.


Le problème actuel, c'est que les opérateurs de << et de >>
signifient surtout l'injection et l'extraction ; c'est leur
utilisation pour le décalage qui représent le surcharge
« abusif ». (Reflechissons un peu, quand même. Quelle est la
signification que le programmeur voir d'abord ? Quelle est
l'utilisation la plus fréquente ?)

acte 2: "x << 1" serait /moins lisible/ que "2 * x"


Pas moins lisible. Il dit quelque chose de différent. On utilise
un quand on veut décaler les bits, et l'autre quand on veut
multiplier une valeur par deux.

ce bavardage (ou avis très personnel) m'amuse un peu, si un
codeur C ne sait pas lire "x << n" je lui conseillerais
humblement de commencer à s'initier au langage qu'il pense
utiliser ou de passer à autre chose.


Apparamment, tu ne sais pas lire le C++, parce qu'il s'agit ici
de multiplier par 2, et en C++, l'opérateur de multiplication
est *, et la constante deux s'écrit 2. Toute autre forme
d'écriture est de l'obfuscation, pûre et simple -- une signe
d'incompétence professionnelle.

niveau performances, rien ne te permet (comme le post de
james kanze te le dit) que x<<1 est plus rapide que 2*x. Sur
un 8086, certes ;-) mais sur un cpu moderne...



du post de James j'ai lu qu'il avait "une fois du mesurer"
mais il a oublié de présenter / commenter le résultat de sa
mesure; donc "rien ne me permet de penser" que son expérience
contredise ce point.


de ma propre expérience, je mesure et constate un gain; et ce
sur petits procs (8086) comme sur Sparc 2 avec Sun CC.


Et là, je ne peut que dire que c'est un mensonge, ou que tu ne
sais pas mesurer, parce que j'ai fait des mesures avec le
compilateur Sun CC, sur Sparc. C'est un compilateur que je
connais bien.

En fait, la technologie pour le faire est bien connu, et ça,
depuis longtemps. Si un compilateur n'effectue pas
l'optimisation aujourd'hui, c'est temps de changer de
compilateur.

pour faire x<<1 il faudra un cycle d'horloge



sur quel proc ? pour un 8086 il en faut 2 ;)


Le 8086 n'est plus fabriqué, à ce que je sache.

pour faire 2*x également



non, en aucun cas, ou plutôt même si un MUL (au moins 70
cycles sur 8086) demandait le même nombre de cycles,
l'opération complète demande ici le chargement de 2 opérandes
(1 registre + 1 reg/mem) (vs valeur immédiate pour un SHL).


Ce sont des problèmes du compilateur. Les compilateurs modernes
les resoudent très bien.

Et même, l'obfuscation n'est permis que dans le cas où rien
d'autre ne marche.

et comment code-t-il (x * 80) ? ((x << 4) + (x << 6))


A priori. Je ne l'ai pas regardé.

ou encore (x * 127) ? génére-t-il: (x << 7) - x ? (pas "- 1" !)


Évidemment. C'est l'exemple que j'ai analysé. Le compilateur Sun
CC génère des instructions qui font un décalage à gauche de
sept, puis enlève la valeur originale. Avec une instruction de
moinos que si on écrit l'expression avec le décalage et la
soustraction.

(Encore plus évident, on ne s'intéresse à de telles bagatelles
que si on a réelement un problème de performance.)

Quoi qu'il en soit, la différence de vitesse AMA ne vaut pas
de perdre la lisibilité du code



je dirais plutôt la qualité du compilo requiert ou non de
coder soi-même ces optimisations; si l'usage d'opérateurs
shift apparait comme "obfuscant" ce sont les aptitudes du
programmeur qu'il faut questionner plus d'une prétendue
lisibilité.


Je questionnerai bien la compétence d'un programmeur qui écrit
un décalage quand il veut multiplier, et vice versa. Un
programme, c'est fait avant tout pour être lu par des hommes.

En fin de compte, on est professionnel, ou on ne l'est pas.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


1 2 3 4 5