[C] Generieke wrapper om fprintf implementeren

Pagina: 1
Acties:

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Ik heb een probleem met het implementeren van toegang tot de standaard
fprintf functie in de library van een programmeertaal. De taal
compileert naar C en alle primitieven van de taal zijn geimplementeerd
door een conversie te doen naar de normale C functie aanroep. De
programmeertaal heeft een universeel data type: ATerm. Van een ATerm
kan je opvragen wat z'n echte type is, bijvoorbeeld Int, Real, Appl.

Nu wil ik dus fprintf toevoegen aan de library, maar ik wil hem niet
zelf implementeren. Ik wil dus gebruik maken van de standaard
fprintf. De conversie van het universele ATerm type naar de types
char*, int of double is hierbij een probleem. De argumenten na
"format" moeten namelijk geconverteerd worden voor de echte aanroep
naar fprintf. Helaas kan ik dit niet voor elkaar krijgen. Het probleem
lijkt hier te zijn dat je argumenten in een va_list niet kan bewerken
voordat je ze doorgeeft aan een andere functie.

Ik heb een paar vieze oplossingen bedacht, die echter allemaal niet
lukken:

Het probleem zou simpel op te lossen zijn als je zelf een va_list mag
opbouwen. Helaas kan dat niet, want er wordt gecontroleerd of de
functie waarin je va_start aanroept een variabel aantal argumenten
heeft, en of de opgegeven variabele het laatste argument met een naam
is. Deze optie valt dus af.

Ik heb ook nog zitten denken over een conversie functie die dan een
void* oplevert, maar dat schiet ook niet op, omdat er ook
geconverteerd moeten kunnen worden naar een int of double.

Ook zou je misschien nog kunnen denken aan conditionele expressies,
maar de 2e en 3e operand van die expressie moeten min of meer
vergelijkbare types hebben: je kan dus niet een char* of een int
opleveren.

Het gedoe met ATermen is misschien wat verwarrend, maar je zou als vergelijkbaar probleem kunnen denken aan het universele data type van strings, waarbij je voor de aanroep naar fprintf de strings wilt converteren naar een int of een double.

Wie heeft er een goed idee?

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Ik ben er vrij zeker van dat je wens onhaalbaar is, tenminste als je uitgaat van standaard C. Als ik je namelijk goed begrijp wil je runtime kunnen bepalen welke argumenten er precies worden meegegeven aan fprintf. Dit is onmogelijk; voor elke mogelijke combinatie van argumenten zou je dan een aparte aanroep moeten hebben. Het is namelijk zo dat voor een gegeven function call expression alle argument-types vaststaan, dus je hebt een bijna oneindig aantal function call expressions nodig. Gebruik je een statische compilatie, dan kun je dat beperken omdat je het maximaal aantal argumenten wel weet, maat zelfs met 3 mogelijke types en 0-5 argumenten heb je al bijna 1000 mogelijkheden.

In C++ liggen de zaken vergelijkbaar; de iostream classes daar hebben het opgelost door maximaal een argument tegelijkertijd te formatten naar een buffer, en de I/O code voor die buffer gescheiden te houden. Dit is bovendien handig als je daarna het equivalent van sprintf moet gaan implementeren.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Is vfprintf niks voor je?
Uit de beschrijving maak ik op dat dat een printf-variant is, maar dan met een argument voor de complete variabele-argumenten-lijst.

edit:

Na het lezen van MSalters' verhaal waarschijnlijk niet

[ Voor 20% gewijzigd door ACM op 05-06-2004 16:23 ]


Verwijderd

Volgens mij doelt de TS meer op vfprintf die een va_list als argument gebruikt (int vfprintf(FILE *stream, const char *format, va_list ap);) dus hij heeft geen probleem met function call expressions. Zijn werkelijke probleem is volgens mij:

1) Je kunt de elementen van een va_list niet typecasten (zonder de stack te smashen).
2) Je kunt nooit weten ik welk type je moet casten zonder toch eerst de format-string te interpreteren.

edit:
2) bij nader inzien toch niet, het echte type van een ATerm is opvraagbaar.

[ Voor 10% gewijzigd door Verwijderd op 05-06-2004 16:40 ]


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Ik was er al bang voor :'( . Helaas zal het dan neerkomen op het herimplementeren van precies dezelfde functionaliteit in een eigen fprintf. Beetje knudde, maar ja. Het is wel jammer dat hij controleert of de opgegeven parameter een parameter van de functie is, want anders zou je volgens mij nog vrij eenvoudig zelf een va_list kunnen samenstellen uit lokale variabelen enzo.

De vfprintf variant lost het probleem idd niet op in dit geval.

Bedankt voor de hulp!

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


Verwijderd

Ik heb hier ook ooit mee geknoeid. Beetje lastig, ben er ook niet uitgekomen.

Het was de bedoeling een wrapper functie te bouwen rondom printf(). Het was de bedoeling dat het zoiets werd:
C:
1
2
3
4
5
6
7
void error(char *file, int *num, char *msg, ...)
{
    printf(msg, ...); /* hoe te doen ?? */
    /* write to syslog */
    /* more debugging stuff */
    exit(0);
}


Het is dus blijkbaar niet mogelijk om een va_list op te bouwen vanuit die ... ?

[ Voor 11% gewijzigd door Verwijderd op 06-06-2004 11:39 ]


  • Radiant
  • Registratie: Juli 2003
  • Niet online

Radiant

Certified MS Bob Administrator

Dat is wel degelijk mogelijk, hier een voorbeeldje:

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void WriteLog( LPCTSTR szFormat, ... )
{
    char szTemp[18024];

    va_list vargs;
    va_start( vargs, szFormat );

    if ( ! vsprintf( szTemp, szFormat, vargs ) )
        strcpy( szTemp, szFormat );

    va_end( vargs );
    
    FILE * fdLog = fopen( "c:\\patchlog.txt", "a" );
    fprintf( fdLog, "%s\n", szTemp );
    fclose( fdLog );
}

Verwijderd

Radiant schreef op 06 juni 2004 @ 11:38:
Dat is wel degelijk mogelijk, hier een voorbeeldje:
...
Het ziet er uit alsof ik dit al wel heb geprobeerd en dat lukte niet. Maar dat is wel enige tijd geleden, dus ik ga eens testen of je gelijk heb. :-) thanx

[ Voor 38% gewijzigd door Verwijderd op 06-06-2004 11:47 ]


Verwijderd

mbravenboer schreef op 05 juni 2004 @ 23:36:
Het is wel jammer dat hij controleert of de opgegeven parameter een parameter van de functie is, want anders zou je volgens mij nog vrij eenvoudig zelf een va_list kunnen samenstellen uit lokale variabelen enzo.
Dit is een denkfout, een va_list is niet zomaar een "los" datatype; een va_list is niet meer dan een pointer naar de call-stack van een functie, m.a.w. een array van alle parameters van de functie zoals ze door de aanroepende functie op de stack geplaatst zijn.

va_start moet dus (het stack-adres van) de eerste parameter meekrijgen, zonder adres is het voor de opgeroepen functie onmogelijk te bepalen waar op de stack de de parameters van de aanroepende functie staan.

De layout van het va_list type wordt dus bepaald door de compiler (die pusht parameters voor functies op de stack) en daarom is het ontzettend riskant zelf va_lists aan te maken. Het kan dus wel (dit werkt onder linux):

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

void do_something(size_t num_params, va_list params)
{
    for(; num_params; --num_params) printf("%4.2f\n", va_arg(params, double));
}

int transform(size_t num_params, ...)
{
    int     i;
    va_list l;
    double  *list= calloc(num_params, sizeof(double));
    if(!list) return 1;
    va_start(l, num_params);
    for(i= 0; i < num_params; ++i) list[i]= (double)va_arg(l, int);
    va_end(l);
    do_something(num_params, (va_list)list);
    free(list);
    return 0;
}

int main(void)
{
    return transform(3, 1, 2, 3);
}

dit vormt dus in list een va_list van doubles uit een va_list van integers; maar dit is zwaar XXX code; als je een va_list van gemengde types moet maken wordt het nog lastiger i.v.m. alignment.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Verwijderd schreef op 06 juni 2004 @ 14:04:
[...]
Dit is een denkfout, een va_list is niet zomaar een "los" datatype; een va_list is niet meer dan een pointer naar de call-stack van een functie, m.a.w. een array van alle parameters van de functie zoals ze door de aanroepende functie op de stack geplaatst zijn.
Dat is een mogelijke vorm, maar op RISC processoren is het niet ongebruikelijk om ook registers te gebruiken voor een vararg call. Het is dus heel wel mogelijk dat er niet een parameter op de fysieke stack staat.
Om dezelfde reden is je aanname dat een va_list een pointer is dus ook ongegrond. Het kan evengoed een structure zijn, met een pointer per type.
va_start moet dus (het stack-adres van) de eerste parameter meekrijgen, zonder adres is het voor de opgeroepen functie onmogelijk te bepalen waar op de stack de de parameters van de aanroepende functie staan.
Dat hangt van de implementatie af, als ik het me goed herinner konden oude UNIX machines al zonder. Echter, het is makkelijker voor de va_start macro een argument te negeren dan er een te orakelen, vandaar dat de standaard toch dat ene soms overbodige argument eist.
De layout van het va_list type wordt dus bepaald door de compiler (die pusht parameters voor functies op de stack) en daarom is het ontzettend riskant zelf va_lists aan te maken. Het kan dus wel (dit werkt onder linux):

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

void do_something(size_t num_params, va_list params)
{
    for(; num_params; --num_params) printf("%4.2f\n", va_arg(params, double));
}

int transform(size_t num_params, ...)
{
    int     i;
    va_list l;
    double  *list= calloc(num_params, sizeof(double));
    if(!list) return 1;
    va_start(l, num_params);
    for(i= 0; i < num_params; ++i) list[i]= (double)va_arg(l, int);
    va_end(l);
    do_something(num_params, (va_list)list);
    free(list);
    return 0;
}

int main(void)
{
    return transform(3, 1, 2, 3);
}

dit vormt dus in list een va_list van doubles uit een va_list van integers; maar dit is zwaar XXX code; als je een va_list van gemengde types moet maken wordt het nog lastiger i.v.m. alignment.
Het is niet eens portable binnen Linux, denk ik. Uit m'n hoofd werkt dit niet op Linux/Itanium, die gebruikt FP registers voor va_list's

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1