8
votes

Comment PrintF connaît-il l'adresse des données de caractères de CSSTRING?

Considérant ce fragment de code:

struct My {
  operator const char*()const{ return "my"; }
} my;

CStringA s( "aha" );
printf("%s %s", s, my );


// another variadic function to get rid of comments about printf :)
void foo( int i, ... ) {
  va_list vars;
  va_start(vars, i);
  for( const char* p = va_arg(vars,const char*)
     ; p != NULL
     ; p=va_arg(vars,const char*) ) 
  {
    std::cout << p << std::endl;
  }
  va_end(vars);
}
foo( 1, s, my );
  • Si l'appel de fonction variable est traduit en appuyant sur les pointeurs des arguments, PrintF recevra un cstringa * code> interprété comme un const char * code> li >
  • Si l'appel de la fonction variadique appelle opérateur (const caractère *) code> dessus, pourquoi ne le feriez-il pas pour ma propre classe? Li> ul>

    Quelqu'un peut-il expliquer cela? P>

    Edit: Ajout d'une fonction variable factice qui traite ses arguments comme const char * code> s. Voici - il se bloque même lorsqu'il atteint le mon code> argument ... p> p>


4 commentaires

@David Heffeman: Je sais. printf est ma fonction variadic prototype. Le code réel que je me réfère est un cstring :: format .


Quelle plate-forme est-ce? Visual Studio n'effectue aucune conversion, il appuie simplement toute la valeur sur la pile. GCC émet une affirmation. Je suppose que vous êtes juste "chanceux", le premier membre de CSstring est un pointeur sur les données.


@Suma: Voulez-vous mettre parier le premier membre de CSTString est un pointeur sur les données exprès ?


Il peut être exprès, mais à des fins exprès de permettre la transmission de fonctions variadiques, comme cela ne fonctionne de toute façon pas - vous pouvez passer un CString de cette façon, mais pas deux.


7 Réponses :


6
votes

Ce que vous faites est un comportement indéfini et est une extension non standard fournie par votre compilateur ou vos œuvres de Porte-Chances. Je suppose que la cString stocke les données de chaîne comme premier élément de la structure, et donc que la lecture de la cstring comme s'il s'agissait d'un char * donne une validité valide Chaîne null terminée.


6 commentaires

Il ne passe pas un cstring * mais un cstring , qui surcharge opérateur (lpctstr) .


Très bien, j'ai raté ça. Dans ce cas, c'est une extension non standard.


Pourquoi est-ce une extension non standard?


S'il n'est pas défini par la norme C ++, alors par définition, il s'agit d'une extension non standard. Je suppose que la norme C ++ ne le définissait pas car les fonctions variadiques étaient considérées comme une maintien de C qui ont été laissées pour la compatibilité en arrière et la surcharge de l'opérateur (comme dans std :: cout ) a été considéré comme une meilleure façon de faire la même chose. Mais c'est juste une supposition.


En effet: après une attention particulière, il semble que le premier membre de la classe de modèle csimplestringt est pxstr m_pszdata . Donc, réinterpréter un cstringt comme const char * est, par pure chance, d'accord. Merci!


Édité pour être correct sur le type étant passé de la valeur, pas d'adresse. Ce comportement ressemblant à des réinterprétes, même si techniquement ub, nécessiterait également la taille de (Cstringa) == Tailleof (Char *), sinon VA_ARG appliquera un décalage incorrect pour d'autres arguments.



1
votes

Si l'appel de la fonction variadique appelle son opérateur (Cons-Char *) dessus, pourquoi ne le feriez-vous pas pour ma propre classe?

Oui, mais vous devriez le jeter explicitement dans votre code: printf ("% s", (lpcstr) s, ...); . .


0 commentaires

0
votes

Votre instruction PrintF est erronée: xxx

devrait être: xxx

qui imprimera "Aha mon".

cstring a un opérateur de convertission pour const char * (c'est en fait pour lpctstr qui est un const tchar * - cstringa a une fonction de conversion pour lpcstr ).

L'appel impression ne convertira pas votre cstringa objet à A cstringa * pointeur. Il le traite essentiellement comme un Void * . Dans le cas de CSSTRING, c'est une chance (ou peut-être la conception de développeurs de Microsoft en tirant parti de quelque chose qui n'est pas dans la norme) qu'il vous donnera la chaîne de résiliation null. Si vous deviez utiliser un _bstr_t à la place (qui a la taille de la chaîne en premier), malgré la fonction de conversion, cela échouerait horriblement.

C'est une bonne pratique (et requis dans de nombreux cas) pour lancer explicitement vos objets / montants sur ce que vous voulez qu'ils soient lorsque vous appelez printf (ou toute fonction variable à ce sujet).


5 commentaires

Il utilise explicitement cstringa de sorte qu'il veut (lpcstr) .


En effet. J'ai ajouté le mon plus tard pour illustrer le point. Je vais éditer la question donc comme cela.


Comment ça va "le jeter à quelque chose qui est utile"? Où est-ce permis?


Printf ne sera pas lancé. Il sera Reterpret_cast . Il ne connaît pas le type de l'argument, donc ne peut pas utiliser l'opérateur de distribution défini dans cstringt .


@Fred: Vous êtes correct. Je vais résoudre mon libellé, mais le principe de base derrière tout cela est que si vous spécifiez ce que vous voulez, il est passé comme dans l'appel de la fonction, c'est un comportement indéfini.



4
votes

Vous ne pouvez pas insérer de données non-POD dans des fonctions variadiques. Plus d'infos


5 commentaires

Vous pouvez, mais c'est un comportement indéfini.


Vous pouvez passer mon, mais pas cstring. Mon est une pod et est donc transmis sans couverture. La fonction appelée n'a aucune idée que c'est mon et ce qu'il devrait faire avec elle.


@shacharptooth: "Vous ne pouvez pas ..." est couramment utilisé pour "Ce serait UB si vous ...", telles que "Vous ne pouvez pas indexer un tableau avec une valeur hors limite".


@Fred Nurk: Oui, mais les gens font quelque chose "tu ne peux pas faire", puis nous plaignez-vous que leur compilateur / exécution n'a pas éventuellement émettre une erreur.


@shacharpôt: Ils méritent de ce qu'ils obtiennent.



1
votes

Si l'appel de fonction variadique est traduit en poussant les pointeurs des arguments, ...

Ce n'est pas la façon dont les fonctions variadiques fonctionnent. Les les valeurs des arguments, plutôt que des indications des arguments, sont transmises, après des règles de conversion spéciales pour des types intégrés (tels que le caractère de l'int).

C ++ 03 §5.2.2P7:

Lorsqu'il n'y a pas de paramètre pour un argument donné, l'argument est transmis de manière à ce que la fonction de réception puisse obtenir la valeur de l'argument en invoquant VA_ARG (18.7). Les conversions standard des conversions standard (4.2) et fonction-à-pointeur (4.2) sont effectuées sur l'expression de l'argument. Après ces conversions, si l'argument n'a pas d'arithmétique, d'énumération, de pointeur, de pointeur à un membre ou de type de classe, le programme est mal formé. Si l'argument a un type de classe non pod (clause 9), le comportement n'est pas défini. Si l'argument a un type d'intégration ou d'énumération soumis aux promotions intégrales (4.5) ou à un type de point flottant soumis à la promotion du point flottant (4.6), la valeur de l'argument est convertie au type promu avant l'appel. . Ces promotions sont appelées promotions d'argument par défaut.

en particulier de ce qui précède:

Si l'argument a un type de classe non pod (clause 9), le comportement est indéfini.

C ++ Punmis à C pour la définition de VA_ARG, et C99 TC3 §7.15.1.1.2P2 dit:

... Si le type n'est pas compatible avec le type de l'argument suivant réel (tel que promu en fonction des promotions d'argument par défaut), le comportement est indéfini, à l'exception des cas suivants: [Liste des cas qui ne s'appliquent pas ici]

Ainsi, si vous passez un type de classe, il doit s'agir de la POD et la fonction de réception doit appliquer le type correct, sinon le comportement n'est pas défini. Cela signifie que dans le pire des cas, cela peut fonctionner exactement comme vous vous attendez.

Printf n'appliquera pas le type correct de tout type de classe défini par l'utilisateur, car il n'a aucune connaissance, de sorte que vous ne pouvez pas transmettre aucun type de classe UDT à imprimerf. Votre FOO fait la même chose en utilisant un pointeur de caractère au lieu du type de classe correct.


1 commentaires

«Dans le pire des cas, cela fonctionne exactement comme vous vous attendez» ... J'aime ça!



8
votes

Le texte pertinent de C ++ 98 standard §5.2.2 / 7:

Le lvalue-to-rvalue (4.1), les conversions standard (4.2) et fonction-à-pointer (4.3) sont effectuées sur l'expression de l'argument. Après ces conversions, si l'argument n'a pas d'arithmétique, d'énumération, de pointeur, de pointeur à un membre ou de type de classe, le programme est mal formé. Si l'argument a un type de classe non pod (clause 9), le comportement est indéfini.

Donc officiellement, le comportement est indéfini .

Cependant, un compilateur donné peut fournir n'importe quel nombre d'extensions de langues et Visual C ++ fait. Le Library MSDN documente le comportement de Visual C ++ comme suit, avec respect Transmettre des arguments à ... :

  • Si l'argument réel est de type float, il est favorisé de taper le double avant l'appel de la fonction.
  • Tout type de caractère signé ou non signé, de type chéri ou énuméré ou de champ de bit est converti en un Int signé ou non signé à l'aide de la promotion intégrale.
  • Tout argument de type de classe est passé par la valeur en tant que structure de données; La copie est créée par copie binaire au lieu d'invoquer le constructeur de copie de la classe (si l'on existe).

    Cela ne mentionne rien à propos de Visual C ++ appliquant des conversions définies par l'utilisateur.

    ms cstring est "intelligemment" porté, de sorte que la représentation de la pod est exactement le pointeur de sa chaîne de caractères NULL terminée. ( Tailleof (CStringa) == Tailleof (Char *) ) Lorsqu'il est utilisé dans tout la fonction de style printf, la fonction vient de recevoir le pointeur de caractères.

    Cela fonctionne donc en raison du dernier point ci-dessus et de la manière dont cstring est mise hors tension.


0 commentaires

1
votes

Ce n'est pas le cas. Cela n'appelle même pas l'opérateur Const de cont * . Visual C ++ passe juste les données de la classe à Printf comme si par memcpy . Cela fonctionne en raison de la disposition de la classe cstring , il ne contient qu'une variable d'un membre qui est un pointeur sur les données de caractères.


0 commentaires