Demat' iNal.
On a l'impression, parfois, que dans un code compilé, pour savoir si une fonction (locale) est utilisée, il suffit de la supprimer, recompiler et si on obtient une erreur, elle était utilisée. Question de bon sens ! D'ailleurs c'est le cas en C [*]
Bien entendu, c'est faux, comme le montre le code C++ suivant:
#include <cstdio>
#ifndef REMOVE
static void foo(int x) {
puts("foo(int)");
}
#endif
static void foo(float y) {
puts("foo(float)");
}
int main() {
foo(1);
}
Si REMOVE
est défini, le code continue à compiler mais a un comportement différent à l'exécution. On peut forger des exemples similaires avec les conversions implicites.
Comme quoi
Le bon sens n'est rien d'autre qu'un ensemble de préjugés qui reposent dans votre esprit avant que vous n'ayez eu 18 ans — (Einstein semble-t-il)
se transpose bien à l'informatique :-)
[*] je pense !
# bah non
Posté par fearan . Évalué à 2 (+1/-2). Dernière modification le 14 novembre 2024 à 13:15.
Et ça ne marche pas non plus en java (je dirai même plus c'est pire), et encore moins en python !
grep et consort restent des outils bien plus fiable pour s'assurer d'éviter de faire des boulettes, les IDE peuvent aussi donner des indications, mais une absence de résultat n'indique pas forcément une absence d'utilisation.
ensuite si la fonction est locale un find in file sera bien plus efficace qu'une compilation.
Et globalement tous les langages permettant introspection ne peuvent reposer sur une méthode aussi basique.
Dison qu'on a pas le même bon sens.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: bah non
Posté par Renault (site web personnel) . Évalué à 4 (+1/-0).
Sinon il y a des outils dédiés type cppcheck qui peuvent faire une telle évaluation et faire un beau rapport à la fin.
Cela évite ce genre de pièges même si cela n'est jamais parfait.
[^] # Re: bah non
Posté par fearan . Évalué à 4 (+1/-0).
Perso je me base surtout de la détection de code mort de l'ide qui doit être une interface ou un réimplémentation de cppcheck. Mais il lui arrive de rater des trucs, notamment a cause de certains template ou de #ifdef / #ifndef.
grep donne un résultat qu'il faut regarder ensuite, mais sur du c++ il ne m'a jamais mis en défaut, comprendre par la des faux positifs; mais jamais une non détection.
Sur le java c'est encore plus compliqué, car avec l’introspection les auto discover et autre y'a pas de solution fiable à 100% (ne surtout pas se fier à grep), encore qu'avec les dernières versions de java il me semble qu'on peut plus accéder aux private via introspection.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: bah non
Posté par freem . Évalué à 3 (+1/-0). Dernière modification le 18 novembre 2024 à 17:46.
D'ailleurs le code de l'OP ne compile pas avec
-Werror=unused-function
, dispo sous gcc et clang quand les 2 fonctions sont la, puisqu'une seule est appelée. Je suis malgré tout surpris de l'absence de warning sur la conversion implicite dans ce cas.[^] # Re: bah non
Posté par arnaudus . Évalué à 6 (+3/-0).
Je n'ai pas trop compris pourquoi c'était censé marcher, on n'aurait pas le même genre de problème avec la redéfinition d'une méthode dans une classe dérivée? Tous les langages qui autorisent la redéfinition / surcharge vont se comporter de cette manière, quand on supprime l'appel le plus spécialisé, le compilateur va appeler l'appel moins spécialisé, etc.
Et en fait c'est même pire pour les langages non-compilés qui vont écraser "silencieusement" les déclarations précédentes, puisque quand on supprime la fonction locale on va aller appeler du code qui était mort auparavant.
Est-ce qu'il y a un contexte où la technique de "je supprime la fonction et ça ne marche plus" fonctionne autrement que par coup de bol?
[^] # Re: bah non
Posté par serge_sans_paille (site web personnel) . Évalué à 2 (+0/-0).
Python n'étant pas vraiment un langage compilé (bien qu'il y ait une étape de compilation avant l'exécution hein), il était hors jeux.
En fait ma remarque fait écho à une intuition courante comme quoi « si on retire un
#include
et que ça compile toujours, alors cetinclude
est inutile ». Un corollaire de mon example est que non.je viens de relire le journal, et à aucun moment il ne mentionne l'avis de l'auteur :-)
[^] # Re: bah non
Posté par fearan . Évalué à 4 (+1/-0).
je ne sais pas d'où vient cette idée, mais quiconque à travaillé sur du C/C++ sait que non c'est pas comme ça que ça marche.
Souvent l'ordre des includes peut faire planter la compilation, des macros peuvent être définie dans les includes; je ne parle même pas du boulet qu'a fait un include guard (#ifndef machin … #define machin), en le recopiant d'un autre include
J'ai même eu un projet où l'ordre des chemins d'includes (défini via la chaine de compilation) changeait le comportement (et oui c'était voulu).
Donc non supprimer une fonction cantonnée au fichier, on vérifie via un search dans ledit fichier, et on vérifie que ce même fichier n'est pas inclus ailleurs car le #include "machin.cc" j'ai déjà vu aussi, et pour plus de sûreté on fait un grep et on analyse les résultats.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: bah non
Posté par totof2000 . Évalué à 3 (+1/-0).
Je crois qe les langages récents (rust/Go) retournent des messages lorsque du code n'est pas utilisé. De mémoire Erlang rale également lorsqu'on a du code mort dans un module. Celà dit je me demande si le compilateur est en mesure de traquer tout le code mort ou s'il a ses limites (j'avoue que je ne me suis jamais posé la question).
[^] # Re: bah non
Posté par arnaudus . Évalué à 3 (+0/-0).
Ça doit dépendre du langage; quand la reflexivité est autorisée (eval()), tu ne peux pas vérifier, et j'imagine qu'il y a des constructions avec des pointeurs de fonction qui rendent le traquage du code mort très très complexe.
Et puis tu as de toutes manières les trucs du genre
if (check_P_equals_NP()) foo();
qui fait que foo() est peut-être mort et peut-être pas…
[^] # Re: bah non
Posté par Thomas Douillard . Évalué à 3 (+0/-0).
Ou plus immédiatement le problème de l'arrêt. Si on sait pas si "P" termine, on sait pas a fortiori, quand on concatène un programme P' pour former PP', si P' est mort ou pas.
[^] # Re: bah non
Posté par arnaudus . Évalué à 3 (+0/-0).
Bon, en fait j'ai quand même répondu avec un peu de mauvaise foi, parce que le compilateur sait ou non s'il a besoin de compiler, et s'il y a un appel dynamique, il compile de toutes manières, j'imagine. Je ne sais pas s'il évalue par exemple si les variables sont potentiellement modifiables
vs
vs
parce que si
void bar(int)
ouvoid bar (const int&)
, alors foo() est mort, mais sivoid bar(int&)
, foo() est peut-être vivant, mais il y a moyen à partir du code de bar() de vérifier si la référence est potentiellement modifiée.[^] # Re: bah non
Posté par Thomas Douillard . Évalué à 3 (+0/-0). Dernière modification le 18 novembre 2024 à 10:56.
Les compilateurs font du :en:Single static assignment de nos jours donc ce type d'analyse est relativement simple je pense (après reformulation, une variable = une valeur, donc truc est toujours égal à 2 dans un tel cas). Après il suffit de propager les constantes et d'évaluer la condition, ici.
On peut aller beaucoup plus loin avec des analyses plus sophistiquées comme l'interprétation symbolique et des solveur SMT comme :en:Z3 Theorem Prover on peut trouver des expérimentations sur ce thème assez facilement avec Z3, tu peux prouver par exemple qu'une condition complexe sur un if est toujours fausse, donc éliminer les arêtes de l'intérieur du if du graphe de flot de contrôle ou graphe d'appel du code, et donc en déduire que le code ne sera jamais utilisé si il est déconnecté en conséquence, avec les éléments dont il dépend au passage.
Ça ne rend pas le problème décidable et les techniques absolument complètes, il y aura toujours des cas ou il sera impossible, mais potentiellement ça peut repousser l'horizon dans bien des cas raisonnables, voire la quasi totalité des cas ordinaires.
[^] # Re: bah non
Posté par arnaudus . Évalué à 3 (+0/-0).
OK, dit comme ça ça semble un peu magique et j'imagine que ça ne gère pas les cas où la variable de départ est modifiée pour retrouver sa valeur initiale, mais de toutes manières quand on parle d'optimisation l'objectif reste quand même pragmatique, ça n'est pas grave si des cas particuliers ne sont pas optimisables…
En fait, l'utilité de tout ça dépend un peu des objectifs, non? S'il te faut 10 secondes de calcul pour déterminer si tu dois compiler ou pas 3 lignes de code, au final tu n'y gagnes pas grand chose…
[^] # Re: bah non
Posté par Renault (site web personnel) . Évalué à 4 (+1/-0).
Sur un logiciels largement distribué ou avec des contraintes fortes il vaut mieux une compilation plus longue qui permet de gagner du temps au runtime que de ne rien faire. De même pour la sûreté, les langages fortement typés par exemple ont une réelle valeur ajoutée à ce sujet.
[^] # Re: bah non
Posté par barmic 🦦 . Évalué à 2 (+0/-0).
Si on va par là à moins d'avoir des builds distincts le mieux c'est encore de supprimer le code source plutôt que de complexifier le build.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: bah non
Posté par barmic 🦦 . Évalué à 2 (+0/-0).
Il me semble que ces optimisations sont par unité de compilation à partir du moment où tu a des linkage même statique, ce n'est plus qu'une analyse de la forme "est-ce que le symbole de ce .o est présent dans un autre .o ? ".
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: bah non
Posté par Thomas Douillard . Évalué à 3 (+0/-0).
C'est le rôle de la propagation de constante. Le SSA transformera du code comme
en
Là dessus, dans un tel cas, on peut déduire (de la même manière que les constexpr (ou la métaprogrammation avec des template) en C++ si tu vois ce que c'est, que x1=4 et donc x2=3 rien qu'en évaluant les expressions.
Évidemment c'est pas possible si il y a trop de non constantes impliquées, mais le côté "propagation" c'est qu'une expression peut devenir constante une fois qu'on en a évalué d'autres.
Ça fait partie des phases que le compilateur fait typiquement, ce genre d'analyse (la propagation de constante et le SSA de nos jours). Les autres font partie d'analyse de code qu'on peut faire par ailleurs pour trouver des bugs par analyse statiques ou pour calculer des lifetime de Rust, et c'est pas inutile dans ce genre de cadre. Potentiellement en conjugaison de ce genre d'analyse c'est un effet de bord "gratuit".
Tu peux faire ça aussi uniquement lors de la phase de compilation finale avant déploiement, et les gains seront partout là ou ça va être déployé, sans forcément faire ça continûment pendant le développement ou ça peut être gênant. Effectivement les gains les plus importants potentiellement sont sur de plus grosses bases de code. C'est aussi les plus dures à analyser et les plus longues à compiler en général.
[^] # Re: bah non
Posté par freem . Évalué à 2 (+0/-0).
Aucune idée, mais LLVM est assez impressionnant de ce point de vue, l'outil (clang-tidy je crois?) sortant même les conditions qui font qu'un code ne peut jamais être exécuté.
Forcément, il y a des limitations, mais bon sang, ça reste balèze et bien utile!
[^] # Re: bah non
Posté par Charly.the.daemon . Évalué à 1 (+2/-0).
Compilé, oui, mais en bytecode pour une machine virtuelle…
Comme java quoi !
# Conversion implicite
Posté par Zenitram (site web personnel) . Évalué à 9 (+7/-0). Dernière modification le 14 novembre 2024 à 13:16.
Tu triches avec les conversions implicites…
Allez :
Avant ton main et c'est bon.
Ou la totale en C++20:
[^] # Re: Conversion implicite
Posté par Tarnyko (site web personnel) . Évalué à 3 (+1/-0). Dernière modification le 14 novembre 2024 à 17:24.
Ouais il triche.
Limite il fait du poutaclique.
Mais comme c'est mon langage de réf', je râle pas -encore- trop ;)
[^] # Re: Conversion implicite
Posté par serge_sans_paille (site web personnel) . Évalué à 3 (+1/-0).
Punaise ! Ça je ne connaissais pas dans ce cadre là, uniquement sur des fonctions membres. C'est super intéressant, merci iniment. C++11 en plus.
# Beurk les conversions implicites
Posté par David Demelier (site web personnel) . Évalué à 4 (+2/-0).
Imaginez une API qui permet de sérialiser des données binaires en fonction du type en entrée :
Résultat possible :
Du coup, si on veut sérialiser des entiers spécifiques à la suite on doit forcer un cast.
Perso je vote pour des spécialisations de template ou des noms explicites (comme
packu64
mais moins C++/template/metaprogramming friendly)git is great because linus did it, mercurial is better because he didn't
[^] # Re: Beurk les conversions implicites
Posté par freem . Évalué à 2 (+0/-0).
Moui. Perso je trouve les trucs avec des noms explicites, genre l'API d'opengl, assez pénibles et justement je trouve que sur ça, C++ est bien agréable.
Par contre, je préférais avoir un warning sur le cas évoqué dans l'OP. J'étais persuadé qu'il était possible d'en avoir un (il y en a, mais sur la fonction non utilisé, ce qui est en effet considérable comme un bug dans le cas en question, mais…) pour les conversions de type implicite, mais apparemment ça ne marche pas dans ce cas précis. Le mot clé
explicit
ne peut pas non plus être utilisé dans ce cas, dommage.[^] # Re: Beurk les conversions implicites
Posté par totof2000 . Évalué à 2 (+0/-0).
le problème dess conversions implicites c'est que ça devient vite une usine à bugs ( bon usine j'exagère paut-être un peu, mais il suffit d'un oubli passager de la règle de conversion pour avoir des choses qui peuvent mal se passer sans qu'on s'en rende compte aux tests).
Sur d'autres points, le nommage explicite est souvent lourd (et parfois inutile), mais pour la conversion je pense que le fait d'être explicite évite des erreurs.
[^] # Re: Beurk les conversions implicites
Posté par freem . Évalué à 3 (+1/-0). Dernière modification le 20 novembre 2024 à 07:58.
Entièrement d'accord, je serais tellement plus serein si le compilo pouvais détecter toutes les conversions implicites… mais ce n'est pas le cas.
Cela dis, il est déjà possible d'en activer un petit paquet. De mémoire, en vrac et non exhaustif:
Ensuite on peut prendre la bonne habitude d'utiliser
explicit
dans les constructeurs, opérateurs d'affection et opérateurs de conversion. Je suis récemment tombé sur un bug a cause d'un … qui avais ajouté un opérateur de conversion implicite vers bool à une classe qui interagit principalement avec des pointers… J'étais en colère quand j'ai fini par comprendre d'où venait le problème.Par pitié, si vous êtes profs ou même pas en fait, s'il vous plaît, insistez auprès des jeunes qu'il *ne faut pas définir de
bool foo::operator() const { return bar; }
: ça ne peut que mal se passer!C'est pas la panacée, certes, mais c'est déjà bien. Et quand un dev C critique cet aspect mais oublie manifestement de mentionner que les cast en C servent quasi à rien, que le C encore récemment permettait des trucs du style:
je me demande quoi rétorquer. Mentionner la mauvaise foi, au bout d'un moment, ça fait un peu réchauffé, même si c'est la réalité.
# et Rust
Posté par abriotde (site web personnel, Mastodon) . Évalué à -1 (+1/-3).
Et Rust se comporte à peu près comme C là dessus alors qu'il a la puissance de C++.
PS comme @serge_sans_paille pour C il y a peut-être des cas tordu ou ce n'est pas le cas.
Sous licence Creative common. Lisez, copiez, modifiez faites en ce que vous voulez.
# Map file
Posté par jsbjr . Évalué à 2 (+1/-0). Dernière modification le 15 novembre 2024 à 09:57.
Pour savoir si une fonction est utilisée en C ou Rust, je demande au linker de générer un Map file.
Le linker de ARM y ajoute une liste des objets qu'il retire car non utilisé
removing symbol blabla
je crois que le ld dois pouvoir montrer ça aussi.Au pire je regarde les symboles inclus dans le binaire avec la commande
nm
.Ça ne marche peut être pas avec les fonctions inline.
[^] # Re: Map file
Posté par David Demelier (site web personnel) . Évalué à 3 (+1/-0).
Et surtout que si c'est une fonction locale à un fichier (donc
static
ou namespace anonyme) le compilateur met un warning si bien configuréPour moi c'est le moyen correct de vérifier qu'une fonction est utilisée ou non plutôt que raboter au hasard.
Autrement, le linker fait les choses bien à ne pas inclure les symbols inutilisés dès lors qu'ils sont pas tous dans le même fichier.
git is great because linus did it, mercurial is better because he didn't
# Polymorphisme
Posté par barmic 🦦 . Évalué à 5 (+3/-0).
Après le polymorphisme rend évident que l'hypothèse n'est pas valide, en tout cas pas dans le cas général.
à la syntaxe prêt
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Polymorphisme
Posté par totof2000 . Évalué à 3 (+3/-2). Dernière modification le 15 novembre 2024 à 15:55.
Mode Chieur = ON
à la faute d'orthographe près ;) (à moins qu'il n'y ait un jeu de mot que je n'ai pas compris )
Mode Chieur = Off
# Bon sens
Posté par Pol' uX (site web personnel) . Évalué à 3 (+1/-0).
En voyant le titre j'imaginais un rappel que l'ordre d'évaluation du C++ est parfois indéfini (contrairement à ce que la graphie pourrait laisser croire).
Adhérer à l'April, ça vous tente ?
[^] # Re: Bon sens
Posté par freem . Évalué à 3 (+1/-0).
Ah oui, je m'en rappelle de celui-la, d'UB… il m'a bien interloqué il y a ~15 ans, quand j'avais écrit un bout de code pour réduire le sucre syntaxique pour utiliser la PBNI (le truc pour injecter du code natif dans cet étron de PowerBuilder): j'avais un comportement différent entre le compilo de VisualStudio et gcc, je comprenais pas pourquoi.
J'avais fait, de mémoire, un truc dans ce style:
result = foo( ++i, ++i, ++i );
et bien sûr, VSC++ faisait tous les incréments avant l'appel, alors que gcc lui faisait ce que je supposais être logique, c'est à direinc EAX; push EAX; inc EAX; push EAX
pour le pseudo assembleur.J'aimerai bien pouvoir remettre la main sur ce bout de code pre-C++11, je me demande comment j'avais réussi certains trucs.
Avec juste des templates (non variadiques à l'époque, et c'est pas plus mal) et quelques macros j'avais réussi a me débarrasser de toutes les redondances de l'API mal branlée de ce RAD pourri qui crash toutes les 30 minutes en moyenne et qui stocke les sources dans un format binaire.
Ce n'était de mémoire pas un code super clean, mais bon, j'aimerai bien le revoir.
Envoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.