64 votes

Techniquement, comment fonctionnent les fonctions variadiques? Comment fonctionne printf?

Je sais que je peux utiliser va_arg d'écrire mon propre variadic fonctions, mais comment faire variadic fonctions de travail sous le capot, c'est à dire sur l'assemblée niveau de l'instruction?

E. g., comment est-il possible qu' printf prend un nombre variable d'arguments?


* Pas de règle sans exception. Il n'y a pas de langage C/C++, cependant, cette question peut être répondue pour les deux

* Remarque: la Réponse donné à l'origine à Comment peut-fonction printf peut prendre des paramètres variables en nombre, tandis que la production?, mais il semble que cela ne s'applique pas à l'interlocuteur

74voto

phresnel Points 20082

Le C et le C++ standard n'ont aucune exigence sur la façon dont il doit fonctionner. Un respect compilateur peut décider d'émettre des listes chaînées, std::stack<boost::any> ou même magique poney poussière (comme par Xeo) sous le capot.

Toutefois, il est généralement mis en œuvre comme suit, même si les transformations comme d'inlining ou de passer des arguments dans les registres du CPU ne peut pas laisser quoi que ce soit de l'examen du code.

Veuillez également noter que cette réponse décrit expressément à la baisse croissante de la pile dans les visuels ci-dessous; aussi, cette réponse est une simplification, simplement pour démontrer le système (veuillez voir https://en.wikipedia.org/wiki/Stack_frame).

Comment une fonction est appelée avec un non-nombre fixe d'arguments

Cela est possible parce que le sous-jacent de l'architecture de la machine a une sorte de "pile" pour chaque thread. La pile est utilisé pour transmettre des arguments de fonctions. Par exemple, lorsque vous avez:

foobar("%d%d%d", 3,2,1);

Alors cette compile un code assembleur comme ceci (exemplaire et schématiquement, code réel peut être légèrement différent); notez que les arguments sont passés de droite à gauche:

push 1
push 2
push 3
push "%d%d%d"
call foobar

Ces push-opérations de remplissage de la pile:

              []   // empty stack
-------------------------------
push 1:       [1]  
-------------------------------
push 2:       [1]
              [2]
-------------------------------
push 3:       [1]
              [2]
              [3]  // there is now 1, 2, 3 in the stack
-------------------------------
push "%d%d%d":[1]
              [2]
              [3]
              ["%d%d%d"]
-------------------------------
call foobar   ...  // foobar uses the same stack!

Le bas de la pile élément est appelé le "Haut de la Pile", souvent abrégé en "TOS".

L' foobar fonction serait désormais accéder à la pile, en commençant à la TOS, c'est à dire la chaîne de format, qui, comme vous vous en souvenez, a été poussé dernier. Imaginez stack est votre pointeur de pile , stack[0] est la valeur du TOS, stack[1] est l'un au-dessus de la CDU, et ainsi de suite:

format_string <- stack[0]

... puis analyse la chaîne de formatage. Lors de l'analyse, il recognozies l' %d-jetons, et, pour chacun, les charge plus de valeur à partir de la pile:

format_string <- stack[0]
offset <- 1
while (parsing):
    token = tokenize_one_more(format_string)
    if (needs_integer (token)):
        value <- stack[offset]
        offset = offset + 1
    ...

Bien sûr, cela est très incomplète pseudo-code qui montre comment la fonction est de s'appuyer sur les arguments passés à savoir combien il a à charge et retirer de la pile.

Sécurité

Cette dépendance à l'égard de l'utilisateur fourni des arguments est également l'un des plus grands problèmes de sécurité (voir la section https://cwe.mitre.org/top25/). Les utilisateurs peuvent facilement utiliser un variadic fonction à tort, soit parce qu'ils n'ont pas lu la documentation, ou oublié de régler le format de la chaîne ou de la liste d'arguments, ou parce qu'ils sont mal plaine, ou quoi que ce soit. Voir également la Chaîne de Format d'Attaque.

C Mise En Œuvre

En C et C++, variadic fonctions sont utilisés avec l' va_list interface. Tout en le poussant sur la pile est intrinsèque à ces langues (en K+R C vous pouvez même l'avant-déclarer une fonction sans en exposant ses arguments, mais encore l'appeler par n'importe quel nombre et le type des arguments), la lecture d'un tel argument inconnu liste est connectée par l' va_...-macros et va_list-type, qui, fondamentalement, les résumés du faible niveau de pile-cadre de l'accès.

7voto

david.pfx Points 5594

Variadic fonctions sont définies par la norme, avec très peu de restrictions explicites. Voici un exemple, la levée de cplusplus.com.

/* va_start example */
#include <stdio.h>      /* printf */
#include <stdarg.h>     /* va_list, va_start, va_arg, va_end */

void PrintFloats (int n, ...)
{
  int i;
  double val;
  printf ("Printing floats:");
  va_list vl;
  va_start(vl,n);
  for (i=0;i<n;i++)
  {
    val=va_arg(vl,double);
    printf (" [%.2f]",val);
  }
  va_end(vl);
  printf ("\n");
}

int main ()
{
  PrintFloats (3,3.14159,2.71828,1.41421);
  return 0;
}

Les hypothèses sont à peu près comme suit.

  1. Il doit y avoir (au moins un) tout d'abord, fixe, argument nommé. L' ... ne fait rien, à l'exception de dire au compilateur de faire la bonne chose.
  2. Le fixe argument(s) fournir des informations sur la façon dont de nombreux variadic arguments il y a, par un quelconque mécanisme.
  3. Depuis le fixe argument, il est possible pour l' va_start macro pour renvoyer un objet qui permet d'arguments pour être récupérées. Le type est - va_list.
  4. De la va_list objet, il est possible pour va_arg d'itérer sur chaque variadic argument, et de contraindre sa valeur dans un type compatible.
  5. Quelque chose de bizarre se serait passé en va_start donc va_end rend les choses encore à droite.

Dans la plus habituelle de la pile en fonction de la situation, l' va_list est simplement un pointeur vers les arguments assis sur la pile, et va_arg incrémente le pointeur, il jette et déréférence à une valeur. Ensuite, va_start initialise le pointeur par certains arithmétique simple (et à l'intérieur de la connaissance) et va_end ne fait rien. Il n'est pas étrange langage d'assemblage, juste un peu à l'intérieur de la connaissance de l'endroit où les choses se trouvent sur la pile. Lire les macros dans la norme en-têtes pour savoir ce que c'est.

Certains compilateurs (MSVC) va nécessiter une séquence d'appel, en vertu de laquelle l'appelant libération de la pile plutôt que le destinataire de l'appel.

Des fonctions comme printf fonctionnent exactement comme cela. Le fixe argument est une chaîne de format, qui permet le nombre d'arguments pour être calculé.

Des fonctions comme vsprintf passer l' va_list objet comme un type d'argument.

Si vous avez besoin de plus ou de niveau inférieur de détails, veuillez ajouter à la question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X