printf avec std :: string?

157

Je crois comprendre que stringc'est un membre de l' stdespace de noms, alors pourquoi ce qui suit se produit-il?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

entrez la description de l'image ici

Chaque fois que le programme s'exécute, myStringimprime une chaîne apparemment aléatoire de 3 caractères, comme dans la sortie ci-dessus.

TheDarkIn1978
la source
8
Juste pour vous le faire savoir, beaucoup de gens critiquent ce livre. Ce que je peux comprendre, car il n'y a pas grand-chose à propos de la programmation orientée objet, mais je ne pense pas que ce soit aussi mauvais que les gens le prétendent.
Jesse Good
ouf! Eh bien, il est bon de garder cela à l'esprit pendant que je me fraye un chemin dans le livre. Je suis sûr que ce ne sera pas le seul livre C ++ que je lirai au cours de l'année prochaine, alors j'espère que cela ne fera pas trop de dégâts :)
TheDarkIn1978
Utiliser l'avertissement le plus élevé du compilateur répondrait à votre question - lors de la compilation avec gcc. Comment MSVC gère cela - je ne sais pas.
Peter VARGA

Réponses:

237

Il est compilé car il printfn'est pas de type sûr, car il utilise des arguments variables au sens C 1 . printfn'a pas d'option pour std::string, seulement une chaîne de style C. Utiliser autre chose à la place de ce à quoi il s'attend ne vous donnera certainement pas les résultats que vous souhaitez. C'est en fait un comportement indéfini, donc tout peut arriver.

Le moyen le plus simple de résoudre ce problème, puisque vous utilisez C ++, est de l'imprimer normalement avec std::cout, car le std::stringprend en charge via la surcharge d'opérateurs:

std::cout << "Follow this command: " << myString;

Si, pour une raison quelconque, vous devez extraire la chaîne de style C, vous pouvez utiliser la c_str()méthode de std::stringpour obtenir une const char *terminaison par null. En utilisant votre exemple:

#include <iostream>
#include <string>
#include <stdio.h>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}

Si vous voulez une fonction similaire printf, mais de type sûr, regardez dans les modèles variadiques (C ++ 11, pris en charge sur tous les principaux compilateurs à partir de MSVC12). Vous pouvez en trouver un exemple ici . Il n'y a rien que je sache d'implémentation comme ça dans la bibliothèque standard, mais il pourrait y en avoir dans Boost, en particulier boost::format.


[1]: Cela signifie que vous pouvez passer n'importe quel nombre d'arguments, mais la fonction compte sur vous pour lui indiquer le nombre et les types de ces arguments. Dans le cas de printf, cela signifie une chaîne avec des informations de type codées comme la %dsignification int. Si vous mentez sur le type ou le nombre, la fonction n'a aucun moyen standard de le savoir, bien que certains compilateurs aient la capacité de vérifier et d'avertir lorsque vous mentez.

Chris
la source
@MooingDuck, bon point. C'est dans la réponse de Jerry, mais étant la réponse acceptée, c'est ce que les gens voient, et ils pourraient partir avant de voir les autres. J'ai ajouté cette option afin d'être la première solution vue, et celle recommandée.
chris
43

Veuillez ne pas utiliser printf("%s", your_string.c_str());

Utilisez cout << your_string;plutôt. Court, simple et sûr. En fait, lorsque vous écrivez du C ++, vous voulez généralement éviter printfcomplètement - c'est un reste de C qui est rarement nécessaire ou utile en C ++.

Quant à savoir pourquoi vous devriez utiliser à la coutplace de printf, les raisons sont nombreuses. Voici un échantillon de quelques-uns des plus évidents:

  1. Comme le montre la question, printfn'est pas sûr de type. Si le type que vous passez diffère de celui donné dans le spécificateur de conversion, printfessaiera d'utiliser tout ce qu'il trouve sur la pile comme s'il s'agissait du type spécifié, donnant un comportement non défini. Certains compilateurs peuvent avertir à ce sujet dans certaines circonstances, mais certains compilateurs ne le peuvent pas / ne le feront pas du tout, et aucun ne le peut dans toutes les circonstances.
  2. printfn'est pas extensible. Vous ne pouvez lui transmettre que des types primitifs. L'ensemble des spécificateurs de conversion qu'il comprend est codé en dur dans son implémentation, et il n'y a aucun moyen pour vous d'ajouter plus / others. La plupart des C ++ bien écrits devraient utiliser ces types principalement pour implémenter des types orientés vers le problème en cours de résolution.
  3. Cela rend le formatage décent beaucoup plus difficile. Pour un exemple évident, lorsque vous imprimez des nombres pour que les gens les lisent, vous voulez généralement insérer des milliers de séparateurs tous les quelques chiffres. Le nombre exact de chiffres et les caractères utilisés comme séparateurs varient, mais cela coutest également couvert. Par exemple:

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;

    Le paramètre régional sans nom (le "") choisit un paramètre régional en fonction de la configuration de l'utilisateur. Par conséquent, sur ma machine (configurée pour l'anglais américain), cela s'imprime au format 123,456.78. Pour quelqu'un qui a son ordinateur configuré pour (par exemple) l'Allemagne, il imprimerait quelque chose comme 123.456,78. Pour quelqu'un qui l'a configuré pour l'Inde, il serait imprimé comme 1,23,456.78(et bien sûr il y en a beaucoup d'autres). Avec printfj'obtenir exactement un résultat: 123456.78. C'est cohérent, mais c'est toujours faux pour tout le monde partout. Essentiellement, la seule façon de contourner ce problème est de faire le formatage séparément, puis de transmettre le résultat sous forme de chaîne à printf, car printflui - même ne fera tout simplement pas le travail correctement.

  4. Bien qu'elles soient assez compactes, les printfchaînes de format peuvent être assez illisibles. Même parmi les programmeurs C qui utilisent printfpratiquement tous les jours, je suppose qu'au moins 99% auraient besoin de regarder les choses pour être sûr de ce que signifie #in %#x, et en quoi cela diffère de ce que signifie #in %#f(et oui, ils signifient des choses complètement différentes ).
Jerry Coffin
la source
11
@ TheDarkIn1978: Vous avez probablement oublié #include <string>. VC ++ a quelques bizarreries dans ses en-têtes qui vous permettront de définir une chaîne, mais pas de l'envoyer cout, sans inclure l'en- <string>tête.
Jerry Coffin
28
@Jerry: Je veux juste souligner que l'utilisation de printf est BEAUCOUP plus rapide que l'utilisation de cout lorsqu'il s'agit de données volumineuses. Donc, ne dites pas que c'est inutile: D
Programmeur
7
@Programmer: voir stackoverflow.com/questions/12044357/… . Résumé: la plupart du temps, c'est coutplus lent, c'est parce que vous avez utilisé std::endllà où vous ne devriez pas.
Jerry Coffin
29
Arrogance typique d'un expert C ++. Si printf existe, pourquoi ne pas l'utiliser?
kuroi neko
6
OK, désolé pour le commentaire accrocheur. Pourtant, printf est assez pratique pour le débogage et les flux, bien que beaucoup plus puissants, ont l'inconvénient que le code ne donne aucune idée de la sortie réelle. Pour la sortie formatée, printf est toujours une alternative viable, et il est dommage que les deux systèmes ne puissent pas mieux coopérer. Juste mon avis bien sûr.
kuroi neko
28

utilisez myString.c_str()si vous voulez qu'une chaîne de type c ( const char*) à utiliser avec printf

Merci

Alessandro Pezzato
la source
6

Utilisez l'exemple std :: printf et c_str ():

std::printf("Follow this command: %s", myString.c_str());
Adel Ben Hamadi
la source
1

La raison principale est probablement qu'une chaîne C ++ est une structure qui inclut une valeur de longueur actuelle, pas seulement l'adresse d'une séquence de caractères terminée par un octet 0. Printf et ses parents s'attendent à trouver une telle séquence, pas une structure, et sont donc confus par les chaînes C ++.

Pour ma part, je crois que printf a une place qui ne peut pas être facilement remplie par les fonctionnalités syntaxiques C ++, tout comme les structures de table en html ont une place qui ne peut pas être facilement remplie par les divs. Comme Dykstra l'a écrit plus tard à propos du goto, il n'avait pas l'intention de créer une religion et ne faisait en réalité que s'opposer à son utilisation comme un kludge pour compenser un code mal conçu.

Ce serait plutôt bien si le projet GNU ajoutait la famille printf à leurs extensions g ++.

MMacD
la source
1

Printf est en fait assez bon à utiliser si la taille compte. Cela signifie que si vous exécutez un programme où la mémoire est un problème, alors printf est en fait une très bonne solution et sous-évaluateur. Cout déplace essentiellement les bits pour faire de la place pour la chaîne, tandis que printf prend juste une sorte de paramètres et les imprime à l'écran. Si vous deviez compiler un simple programme hello world, printf serait capable de le compiler en moins de 60 000 bits par opposition à cout, il faudrait plus d'un million de bits à compiler.

Pour votre situation, id suggère d'utiliser cout simplement parce qu'il est beaucoup plus pratique à utiliser. Cependant, je dirais que printf est quelque chose de bon à savoir.

Howard Howard
la source
1

printfaccepte un nombre variable d'arguments. Ceux-ci ne peuvent avoir que des types POD (Plain Old Data). Code qui transmet autre chose que POD à printfla compilation uniquement car le compilateur suppose que vous avez le bon format. %ssignifie que l'argument respectif est supposé être un pointeur vers a char. Dans votre cas, c'est un std::stringnon const char*. printfne le sait pas car le type d'argument est perdu et est censé être restauré à partir du paramètre format. Lorsque vous transformez cet std::stringargument en const char*pointeur résultant, vous pointez vers une région de mémoire non pertinente au lieu de la chaîne C souhaitée. Pour cette raison, votre code imprime du charabia.

Bien que ce printfsoit un excellent choix pour imprimer du texte formaté (surtout si vous avez l'intention d'avoir un remplissage), cela peut être dangereux si vous n'avez pas activé les avertissements du compilateur. Activez toujours les avertissements, car de telles erreurs sont facilement évitables. Il n'y a aucune raison d'utiliser le std::coutmécanisme maladroit si la printffamille peut faire la même tâche d'une manière beaucoup plus rapide et plus jolie. Assurez-vous simplement que vous avez activé tous les avertissements ( -Wall -Wextra) et tout ira bien. Si vous utilisez votre propre printfimplémentation personnalisée, vous devez la déclarer avec le __attribute__mécanisme qui permet au compilateur de vérifier la chaîne de format par rapport aux paramètres fournis .

Hyène
la source