printf - source de bugs? [fermé]

9

J'utilise beaucoup de printffins de traçage / journalisation dans mon code, j'ai trouvé que c'est une source d'erreur de programmation. J'ai toujours trouvé que l'opérateur d'insertion ( <<) était quelque chose d'étrange, mais je commence à penser qu'en l'utilisant à la place, je pourrais éviter certains de ces bogues.

Quelqu'un a déjà eu une révélation similaire ou je saisis juste des pailles ici?

Certains emportent des points

  • Ma pensée actuelle est que la sécurité de type l'emporte sur tout avantage d'utiliser printf. Le vrai problème est la chaîne de formatage et l'utilisation de fonctions variadiques non sécurisées.
  • Peut-être que je n'utiliserai pas <<les variantes du flux de sortie stl mais je vais certainement chercher à utiliser un mécanisme de type sûr qui est très similaire.
  • Une grande partie du traçage / journalisation est conditionnelle, mais je voudrais toujours exécuter le code pour ne pas manquer les bogues dans les tests simplement parce que c'est une branche rarement prise.
John Leidegren
la source
4
printfdans le monde C ++? Il me manque quelque chose ici?
user827992
10
@ user827992: Vous manquez le fait que la norme C ++ inclut la bibliothèque standard C par référence? Il est parfaitement légal de l'utiliser printfen C ++. (Que ce soit une bonne idée est une autre question.)
Keith Thompson
2
@ user827992: printfa quelques avantages; voir ma réponse.
Keith Thompson
1
Cette question est assez limite. Les questions "Que pensez-vous les gars" sont souvent fermées.
dbracey
1
@vitaut je suppose (merci pour l'astuce). Je suis juste un peu perplexe devant la modération agressive. Cela n'encourage pas vraiment des discussions intéressantes sur les situations de programmation, ce que j'aimerais avoir davantage.
John Leidegren du

Réponses:

2

printf, en particulier dans les cas où vous pourriez vous soucier des performances (comme sprintf et fprintf) est un hack vraiment étrange. Cela m'étonne constamment que les gens qui martèlent le C ++ en raison de la surcharge de performance minuscule liée aux fonctions virtuelles continueront ensuite à défendre l'io de C.

Oui, afin de comprendre le format de notre sortie, quelque chose que nous pouvons savoir à 100% au moment de la compilation, analysons une chaîne de format Fricken au moment de l'exécution dans une table de saut massivement bizarre en utilisant des codes de format impénétrables!

Bien sûr, ces codes de format ne pouvaient pas être faits pour correspondre aux types qu'ils représentent, ce serait trop facile ... et vous êtes rappelé à chaque fois que vous recherchez si c'est% llg ou% lg que cette langue (fortement typée) vous rend déterminer manuellement les types pour imprimer / numériser quelque chose, ET a été conçu pour les processeurs pré-32 bits.

J'admets que la gestion de la largeur et de la précision du format par C ++ est volumineuse et pourrait utiliser du sucre syntaxique, mais cela ne signifie pas que vous devez défendre le hack bizarre qui est le principal système io de C. Les bases absolues sont assez faciles dans les deux langues (bien que vous devriez probablement utiliser quelque chose comme une fonction d'erreur personnalisée / un flux d'erreur pour le code de débogage), les cas modérés sont de type regex en C (faciles à écrire, difficiles à analyser / déboguer ), et les cas complexes impossibles en C.

(Si vous utilisez des conteneurs standard, écrivez-vous des surcharges d'opérateur rapide << qui vous permettent de faire des choses comme std::cout << my_list << "\n";pour le débogage, où ma_liste est de type list<vector<pair<int,string> > >.)

jkerian
la source
1
Le problème de la bibliothèque C ++ standard est que la plupart des incarnations implémentent operator<<(ostream&, T)en appelant ... enfin sprintf,! Les performances de sprintfne sont pas optimales, mais de ce fait, les performances des iostreams sont généralement encore pires.
Jan Hudec
@JanHudec: Cela n'a pas été vrai depuis environ une décennie à ce stade. L'impression réelle se fait avec les mêmes appels système sous-jacents, et les implémentations C ++ font souvent appel à des bibliothèques C pour cela ... mais ce n'est pas la même chose que le routage std :: cout via printf.
jkerian
16

Mélanger une sortie de style C printf()(ou puts()ou putchar()ou ...) avec une sortie de style C ++ std::cout << ...peut être dangereux. Si je me souviens bien, ils peuvent avoir des mécanismes de mise en mémoire tampon distincts, de sorte que la sortie peut ne pas apparaître dans l'ordre prévu. (Comme AProgrammer le mentionne dans un commentaire, sync_with_stdiotraite de cela).

printf()est fondamentalement dangereux pour les types. Le type attendu pour un argument est déterminé par la chaîne de format ( "%d"nécessite un intou quelque chose qui promeut int, "%s"nécessite un char*qui doit pointer vers une chaîne de style C correctement terminée, etc.), mais le fait de passer le mauvais type d'argument entraîne un comportement indéfini , pas une erreur diagnostiquable. Certains compilateurs, tels que gcc, font un assez bon travail d'avertissement sur les incompatibilités de type, mais ils ne peuvent le faire que si la chaîne de format est un littéral ou est autrement connue au moment de la compilation (ce qui est le cas le plus courant) - et tel les avertissements ne sont pas requis par la langue. Si vous passez le mauvais type d'argument, des choses arbitrairement mauvaises peuvent se produire.

Les E / S de flux de C ++, en revanche, sont beaucoup plus sécuritaires <<pour les types , car l' opérateur est surchargé pour de nombreux types différents. std::cout << xn'a pas besoin de spécifier le type de x; le compilateur générera le bon code pour tout type de xfichier.

D'un autre côté, printfles options de formatage de sont à mon humble avis beaucoup plus pratiques. Si je veux imprimer une valeur à virgule flottante avec 3 chiffres après la virgule décimale, je peux l'utiliser "%.3f"- et cela n'a aucun effet sur les autres arguments, même dans le même printfappel. Les C ++ setprecision, d'autre part, affectent l'état du flux et peuvent gâcher la sortie ultérieure si vous ne faites pas très attention à restaurer le flux à son état précédent. (Ceci est ma bête noire personnelle; si je manque un moyen propre de l'éviter, veuillez commenter.)

Les deux ont des avantages et des inconvénients. La disponibilité de printfest particulièrement utile si vous avez un arrière-plan C et que vous le connaissez mieux, ou si vous importez du code source C dans un programme C ++. std::cout << ...est plus idiomatique pour C ++, et n'exige pas autant de soin pour éviter les décalages de type. Les deux sont valides C ++ (la norme C ++ inclut la plupart de la bibliothèque standard C par référence).

Il est probablement préférable de l'utiliser std::cout << ...pour le plaisir d'autres programmeurs C ++ qui peuvent travailler sur votre code, mais vous pouvez utiliser l'un ou l'autre - en particulier dans le code de trace que vous allez jeter.

Et bien sûr, cela vaut la peine de passer du temps à apprendre à utiliser les débogueurs (mais cela pourrait ne pas être possible dans certains environnements).

Keith Thompson
la source
Aucune mention de mixage dans la question d'origine.
dbracey
1
@dbracey: Non, mais j'ai pensé que cela valait la peine d'être mentionné comme un inconvénient possible de printf.
Keith Thompson
6
Pour le problème de synchronisation, voir std::ios_base::sync_with_stdio.
Programmeur
1
+1 L'utilisation de std :: cout pour imprimer les informations de débogage dans une application multithread est 100% inutile. Au moins avec printf, les choses ne sont pas aussi susceptibles d'être entrelacées et insaisissables par l'homme ou la machine.
James
@James: Est-ce parce que std::coututilise un appel distinct pour chaque élément imprimé? Vous pouvez contourner cela en collectant une ligne de sortie dans une chaîne avant de l'imprimer. Et bien sûr, vous pouvez également imprimer un élément à la fois avec printf; il est juste plus pratique d'imprimer une ligne (ou plus) en un seul appel.
Keith Thompson
2

Votre problème vient très probablement du mélange de deux gestionnaires de sortie standard très différents, chacun ayant son propre programme pour ce pauvre petit STDOUT. Vous obtenez aucune garantie sur la façon dont ils sont mis en œuvre, et il est tout à fait possible qu'ils mettent en conflit d' options de descripteurs de fichiers, à la fois essayer de faire des choses différentes à elle, etc. En outre, les opérateurs d'insertion ont une importante plus printf: printfvous permettra de faire ceci:

printf("%d", SomeObject);

Alors que <<non.

Remarque: pour le débogage, vous n'utilisez pas printfou cout. Vous utilisez fprintf(stderr, ...)et cerr.

Linuxios
la source
Aucune mention de mixage dans la question d'origine.
dbracey
Bien sûr, vous pouvez imprimer l'adresse d'un objet, mais la grande différence est que le printftype n'est pas sûr et je pense que la sécurité du type l'emporte sur tout avantage de l'utilisation printf. Le problème est vraiment la chaîne de format et la fonction variadique non sécurisée.
John Leidegren
@JohnLeidegren: Et si ce SomeObjectn'était pas un pointeur? Vous allez obtenir des données binaires arbitraires que le compilateur décide de représenter SomeObject.
Linuxios
Je pense que j'ai lu votre réponse à l'envers ... nvm.
John Leidegren
1

Il existe de nombreux groupes - par exemple Google - qui n'aiment pas les flux.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Ouvrez le triangle pour que vous puissiez voir la discussion.) Je pense que le guide de style google C ++ a BEAUCOUP de conseils très judicieux.

Je pense que le compromis est que les flux sont plus sûrs mais printf est plus clair à lire (et plus facile d'obtenir exactement le formatage que vous souhaitez).

dbracey
la source
2
Le guide de style Google est sympa, MAIS il contient pas mal d'éléments qui ne conviennent pas à un guide à usage général . (ce qui est correct, car après tout, c'est le guide de Google pour le code exécuté sur / pour Google.)
Martin Ba
1

printfpeut provoquer des bogues en raison du manque de sécurité de type. Il existe plusieurs façons de résoudre ce problème sans passer à iostreaml' <<opérateur de et à un formatage plus compliqué:

  • Certains compilateurs (tels que GCC et Clang) peuvent éventuellement vérifier vos printfchaînes de format par rapport aux printfarguments et peuvent afficher des avertissements tels que les suivants s'ils ne correspondent pas.
    avertissement: la conversion spécifie le type 'int' mais l'argument a le type 'char *'
  • Le script typesafeprintf peut prétraiter vos printfappels -style pour les rendre sûrs pour le type.
  • Des bibliothèques telles que Boost.Format et FastFormat vous permettent d'utiliser des printfchaînes de format de type similaire (les Boost.Format en particulier sont presque identiques à printf) tout en conservant iostreamsla sécurité et l'extensibilité du type.
Josh Kelley
la source
1

La syntaxe de Printf est fondamentalement correcte, moins une saisie obscure. Si vous pensez que c'est mal pourquoi C #, Python et d'autres langages utilisent la construction très similaire? Le problème en C ou C ++: il ne fait pas partie d'un langage et n'est donc pas vérifié par le compilateur pour la syntaxe correcte (*) et n'est pas décomposé en série d'appels natifs si l'optimisation de la vitesse. Notez que si vous optimisez la taille, les appels printf pourraient s'avérer plus efficaces! La syntaxe de streaming C ++ est à mon humble avis tout sauf bonne. Cela fonctionne, la sécurité de type est là, mais la syntaxe verbeuse ... blague. Je veux dire que je l'utilise, mais sans joie.

(*) certains compilateurs font cette vérification ainsi que presque tous les outils d'analyse statique (j'utilise Lint et je n'ai jamais eu de problème avec printf depuis).

Mar
la source
1
Il existe Boost.Format qui combine la syntaxe pratique ( format("fmt") % arg1 % arg2 ...;) avec la sécurité de type. Au prix de quelques performances supplémentaires, car il génère des appels de chaîne de caractères qui génèrent en interne des appels sprintf dans de nombreuses implémentations.
Jan Hudec
0

printfest, à mon avis, un outil de sortie beaucoup plus flexible pour traiter les variables que n'importe quelle sortie de flux CPP. Par exemple:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Cependant, lorsque vous voudrez peut-être utiliser l' <<opérateur CPP , c'est lorsque vous le surchargez pour une méthode particulière ... par exemple pour obtenir le vidage d'un objet qui contient les données d'une personne particulière, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Pour cela, il serait beaucoup plus efficace de dire (en supposant qu'il as'agit d'un objet de PersonData)

std::cout << a;

que:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

La première est beaucoup plus conforme au principe d'encapsulation (pas besoin de connaître les spécificités, les variables de membre privé), et est également plus facile à lire.

Aviator45003
la source
0

Vous n'êtes pas censé utiliser printfen C ++. Déjà. La raison est, comme vous l'avez correctement noté, que c'est une source de bogues et le fait que l'impression de types personnalisés, et en C ++ presque tout devrait être des types personnalisés, est pénible. La solution C ++ est les flux.

Cependant, il existe un problème critique qui rend les flux inappropriés pour toute sortie visible par l'utilisateur! Le problème, ce sont les traductions. Un exemple d'emprunt du manuel gettext dit que vous voulez écrire:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Maintenant, le traducteur allemand arrive et dit: Ok, en allemand, le message devrait être

n Zeichen lang ist die Zeichenkette ' s '

Et maintenant vous avez des ennuis, car il a besoin que les morceaux soient mélangés. Il faut dire que même de nombreuses mises en œuvre printfont des problèmes avec cela. À moins qu'ils ne prennent en charge l'extension afin que vous puissiez utiliser

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Le Boost.Format prend en charge les formats de style printf et dispose de cette fonctionnalité. Vous écrivez donc:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Malheureusement, cela entraîne un peu de pénalité en termes de performances, car en interne, il crée un flux de chaînes et utilise l' <<opérateur pour formater chaque bit et dans de nombreuses implémentations, l' <<opérateur appelle en interne sprintf. Je soupçonne une mise en œuvre plus efficace serait possible si vraiment souhaité.

Jan Hudec
la source
-1

Vous faites beaucoup de travail inutile, à côté du fait que stlc'est mauvais ou non, déboguez votre code avec une série de printfseulement ajouter 1 niveau supplémentaire d'échecs possibles.

Utilisez simplement un débogueur et lisez quelque chose sur les exceptions et comment les attraper et les lancer; essayez de ne pas être plus verbeux que ce dont vous avez réellement besoin.

PS

printf est utilisé en C, pour le C ++ que vous avez std::cout

user827992
la source
Vous n'utilisez pas le traçage / journalisation au lieu d'un débogueur.
John Leidegren