0
votes

Quelle est la manière multiplateforme de transmettre une va_list par référence?

J'ai écrit une fonction qui accepte une va_list , et qui est censée être appelée de manière itérative par son appelant. Il devrait modifier la va_list et les changements devraient persister dans l'appelant afin que le prochain appel à la fonction se poursuive avec l'argument suivant.

Je ne peux pas publier ce code spécifiquement, mais voici un extrait de code qui reproduit la situation ( lien godbolt ):

#include <stdarg.h>
#include <stdio.h>

void print_integer(va_list *ap) {
    printf("%i\n", va_arg(*ap, int));
}

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer(&ap);
    }
}

void print_integers(int count, ...) {
    va_list ap;
    va_start(ap, count);
    vprint_integers(count, ap);
    va_end(ap);
}

int main() {
    print_integers(3, 1, 2, 3);
}

Cela fonctionne (imprime "1 2 3") sur ma plate-forme x86 car va_list est passé par "reference" (il est probablement déclaré comme un tableau d'un élément, donc va_list code> les arguments se désintègrent en un pointeur). Cependant, cela ne fonctionne pas sur ma plate-forme ARM (affiche "1 1 1"), où va_list semble être défini comme un pointeur vers quelque chose. Sur cette plate-forme, va_arg renvoie toujours le premier argument.

La meilleure option suivante semble être de faire de ap un pointeur:

XXX

Cela fonctionne sur ARM (avec va_arg (* ap, ...) ), mais il ne se compile pas sur x86. Quand j'essaye print_integer (& ap) sur x86, Clang dit:

erreur: types de pointeurs incompatibles passant ' struct __va_list_tag ** ' au paramètre de type ' va_list * ' (alias ' __builtin_va_list * ' )

Cela ne semble se produire que lorsque vous prenez l'adresse d'une va_list passée en argument, pas lorsqu'elle est extraite d'une variable locale. Malheureusement, j'ai besoin de ma variante v pour prendre un objet va_list et non un pointeur vers celui-ci.

Il est facile d'obtenir une valeur multiplateforme cohérente sémantique pour va_list en utilisant va_copy . Existe-t-il un moyen multiplateforme d'obtenir une sémantique de référence cohérente pour va_list?

c

6 commentaires

Pouvons-nous voir va_do_stuff ? La manière dont vous utilisez va_list est probablement importante.


@Schwern, vous ne pouvez malheureusement pas. Voici un exemple si cela vous aide à comprendre de quoi je parle: il affiche "1 2 3" sur x86 et "1 1 1" sur ARM, et mon objectif est de transmettre la va_list de telle sorte qu'elle imprime également "1 2 3" sur ARM. En dehors de cela, je ne sais pas avec quoi va_do_stuff pourrait vous aider davantage pour répondre à cette question.


@zneak Cela devrait être inclus dans la question, ainsi que la version utilisant va_list * ap


@zneak Nous n'avons pas besoin de voir l'original, juste un MCVE qui illustre le problème. Votre exemple est utile, mais nous devons également voir comment vous modifiez va_list .


La question a été mise à jour.


Similaire: stackoverflow.com/questions/8047362/...


3 Réponses :


0
votes

Vous n'avez peut-être pas de chance. 7.15 (3) dit que ce que vous faites a des effets indéterminés.

L'objet ap peut être passé comme argument à une autre fonction; si cette fonction invoque la macro va_arg avec le paramètre ap , la valeur de ap dans la fonction appelante est indéterminée et doit être passée au va_end avant toute autre référence à ap .

L'appel à va_arg dans print_integer fait que ap dans print_integers est indéterminé. Sur une architecture, elle est incrémentée, sur une autre non.

Quant à ce qu'est va_list , cela pourrait être n'importe quoi ...

... qui est un type d'objet adapté pour contenir les informations nécessaires aux macros va_start, va_arg, va_end et va_copy.

Vous souhaiterez peut-être envisager une approche différente pour résoudre le problème sous-jacent .


4 commentaires

Une chose que je n'aime pas dans les problèmes x-y est qu'il semble y avoir une supposition que OP ne sait pas vraiment quel est son problème. Demander un cas de repro minimum est une excellente étape sur laquelle je n'ai pas passé assez de temps. D'un autre côté, je pense que pour la plupart des gens qui écrivent du code de manière professionnelle, dire aux autres "vous ne pouvez pas faire ça" est généralement perçu comme plus respectueux de leurs capacités que de leur dire "Je pourrais aider si vous m'a dit plus sur vos besoins ". Mes besoins sont assez vieux pour boire.


ap dans print_integers n'est pas utilisé après avoir été indéterminé, il n'y a donc pas de problème à cet égard. Il est courant de passer va_list "par valeur" (en tant que tel) à une fonction, et bien sûr de ne pas continuer à l'utiliser dans le contexte d'appel.


@zneak Un professionnel ne le prend pas personnellement lorsqu'on lui demande d'envisager une solution oblique à un problème difficile. Si ce n'est pas un problème XY, ils disent "ce n'est pas un problème XY", ou ils ne disent rien du tout. J'espère que vous résolvez votre problème.


@Schwern, si mon commentaire vous a frotté dans le mauvais sens, pensez à vous demander pourquoi vous estimez que je ne suis pas justifié de critiquer vos communications alors que vous vous trouvez justifié de critiquer la mienne.



1
votes

Le problème ici est que va_list , sur la plate-forme x86, est défini comme un tableau de 1 élément (appelons-le __va_list_tag [1] ). Il se décompose en pointeur lorsqu'il est accepté comme argument, donc & ap est très différent selon que ap est un paramètre de la fonction ( __va_list_tag ** code >) ou une variable locale ( __va_list_tag (*) [1] ).

Une solution qui fonctionne dans ce cas est simplement de créer une va_list locale, utilisez va_copy

pour le remplir, et passez un pointeur vers cette va_list locale. ( godbolt )
void vprint_integers(int count, va_list ap) {
    va_list local;
    va_copy(local, ap);
    for (int i = 0; i < count; ++i) {
        print_integer(&local);
    }
    va_end(local);
}

Dans mon cas, vprint_integers et va_copy sont nécessaires car l'interface de vprint_integers accepte une va_list et cela ne peut pas changer. Avec des exigences plus flexibles, changer vprint_integers pour accepter un pointeur va_list convient également.

va_list n'est pas spécifié pour être quelque chose en particulier, mais il est défini comme étant un type d'objet, il n'y a donc pas vraiment de raison de croire que vous ne pouvez pas prendre ou passer son adresse. Une autre solution très similaire qui contourne complètement la question de savoir si vous pouvez prendre l'adresse d'une va_list consiste à envelopper la va_list dans une structure et à passer un pointeur vers cette structure.


0 commentaires

0
votes

Le problème dans le deuxième programme est que si va_list est un type de tableau, alors le paramètre de fonction ap a son type ajusté pour être un type de pointeur. L'argument a donc un niveau d'indirection différent dans chaque cas.

Depuis C11, nous pouvons résoudre cela avec un sélecteur générique pour tester si ap est toujours une va_list :

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer((va_list *)_Generic(&ap, va_list*: &ap, default: ap));
    }

Cette solution a été inspirée par cette réponse qui utilisait un macro pour les deux cas nécessitant une configuration manuelle.

Remarques sur le _Generic:

  • Nous devons tester & ap car, depuis C18, la décroissance tableau-pointeur est effectuée sur la première expression.
  • À l'origine, j'avais _Generic (& ap, va_list *: & ap, default: (va_list *) ap) mais cela est rejeté par les compilateurs qui vérifient les violations de contraintes dans toutes les branches pour toutes les entrées - bien que ne continuez pas à vérifier les violations de contrainte dans les expressions environnantes pour les branches non sélectionnées.


7 commentaires

Cela ne s'appuie pas sur certaines plates-formes x86 (notamment la mienne et celle qui exécute godbolt ). C'est la première chose que j'ai essayée.


@zneak oh, le problème est que le paramètre de fonction ap n'a plus le type va_list en raison de l'ajustement. Je réviserai ma réponse


J'ai publié ce que vous êtes probablement sur le point de publier il y a quelques minutes. :)


@zneak J'avais autre chose en tête, il y a plus d'une façon d'écorcher un chat :)


@zneak ma réponse est maintenant géniale ou horrible, pouvez-vous la tester sur votre plateforme?


Cela fonctionnerait pour tous les ABI que Clang soutient, je crois. D'un autre côté, j'aime la certitude du début et de la fin de la sémantique de référence avec l'approche va_copy.


@zneak OK. J'ai réussi à résoudre le problème d'origine dans ma réponse avec violation de contrainte