Anzeige:
Ergebnis 1 bis 10 von 10

Thema: Variable Length Parameter Lists

Hybrid-Darstellung

Vorheriger Beitrag Vorheriger Beitrag   Nächster Beitrag Nächster Beitrag
  1. #1
    Registrierter Benutzer
    Registriert seit
    05.06.2003
    Beiträge
    118

    Variable Length Parameter Lists

    Hallo!

    Beim Durchlesen des FAQ zur comp.lang.c fiel mein Blick auf die VarArgs-Geschichte. Seitdem frage ich mich, wie das System das stackseitig implementiert. Abhängig davon, ob PASCAL-Aufrufkonventionen genutzt werden, liegen doch Parameter auf dem Stack, die vom Aufgerufenen vom stack geholt werden. Leider ist printf für meine Frage ein schlechtes Beispiel, da das Parsen des 1. Parameters die Anzahl der erwarteten Argumente identifiziert und mir ein besseres Beispiel fehlt. Wie genau weiß der Aufgerufene, wann er fertig ist mit Abholen der Parameter?

    Wir haben einen Teil der Antwort gefunden. Bei Programmen, die zur Compilezeit komplett bekannt sind, tritt das Problem nicht auf, da sich der Compiler die Argumentlisten zusammensammelt. Wie steht's jedoch mit exportierten Funktionen? Oder können diese Argumentlisten nicht extern angeboten werden?

    AD!

  2. #2
    Registrierter Benutzer
    Registriert seit
    24.06.2003
    Beiträge
    486

    Re: Variable Length Parameter Lists

    Original geschrieben von Thomas Engelke
    Leider ist printf für meine Frage ein schlechtes Beispiel, da das Parsen des 1. Parameters die Anzahl der erwarteten Argumente identifiziert und mir ein besseres Beispiel fehlt. Wie genau weiß der Aufgerufene, wann er fertig ist mit Abholen der Parameter?
    Dein printf Bsp ist nicht so schlecht, wie du denkst.
    Auf irgendeine Art muß die Funktion ermitteln können, wieviele Argument sie zu erwarten hat.
    Die häufigste Variante ist dabei nunmal, daß ein regulärer Parameter der Funktion die Anzahl explizit oder implizit enthält.
    Eine andere Möglichkeit ist, daß man mit einem speziellen 'Endezeichen' arbeitet.
    Bsp, eine Funktion, die char* (Strings) übergeben bekommt, und den Abschluß bildet ein NULL Pointer (beim Aufruf in char* gecastet,wichtig).
    Das passiert also zur Laufzeit, daher hab' ich deinen letzten Absatz in dem Zusammenhang nicht ganz verstanden.

  3. #3
    Registrierter Benutzer
    Registriert seit
    05.06.2003
    Beiträge
    118

    Re: Re: Variable Length Parameter Lists

    Original geschrieben von wraith
    ... und den Abschluß bildet ein NULL Pointer (beim Aufruf in char* gecastet,wichtig) ...
    Wie stelle ich mir das auf Systemebene vor? Wie liegen denn die Parameter auf dem Stack? Wie kann dann der Aufgerufene zwischen einem 0-Pointer als Wert und dem vereinbarten Endewert unterscheiden? Der "Trick", wie ich ihn von getch() kenne (Rückgabewert als signed int definieren, damit ich ein -1 mitbekommen kann, welcher kein gültiger Wert ist), kann ja hier nicht funktionieren, da doch eh mit 32bit-Werten gearbeitet wird und es imho keinen Wert gibt, der nicht auch ein valider Parameterwert sein könnte.

    AD!

  4. #4
    Registrierter Benutzer
    Registriert seit
    24.06.2003
    Beiträge
    486

    Re: Re: Re: Variable Length Parameter Lists

    Original geschrieben von Thomas Engelke
    Wie stelle ich mir das auf Systemebene vor? Wie liegen denn die Parameter auf dem Stack? Wie kann dann der Aufgerufene zwischen einem 0-Pointer als Wert und dem vereinbarten Endewert unterscheiden? Der "Trick", wie ich ihn von getch() kenne (Rückgabewert als signed int definieren, damit ich ein -1 mitbekommen kann, welcher kein gültiger Wert ist), kann ja hier nicht funktionieren, da doch eh mit 32bit-Werten gearbeitet wird und es imho keinen Wert gibt, der nicht auch ein valider Parameterwert sein könnte.
    Erstmal ein paar Beispiel Funktionen.

    Eine Funktion,die eine beliebige Anzahl Strings ausgibt, abgeschlossen durch einen Null Pointer
    Code:
    void printChars(char *arg, ...)
    {
    	char *str;
    	va_list ap;
    	va_start(ap,arg);
    	puts(arg);
    	str = va_arg(ap,char*);
    	while(str)
    	{
    		puts(str);
    		str = va_arg(ap,char*);
    	}
    	va_end(ap);
    }
    ...
    printChars("Hello","World","!",(char*)0);
    Eine Funktion, die eine beliebige Anzahl ints ausgibt, abgeschlossen durch eine -1
    Code:
    void printInts(int arg, ...)
    {
    	int num;
    	va_list ap;
    	va_start(ap,arg);
    	printf("%d\n",arg);
    	num = va_arg(ap,int);
    	while(num != -1)
    	{
    		printf("%d\n",num);
    		num = va_arg(ap,int);
    	}
    	va_end(ap);
    }
    ...
    printInts(1,2,3,4,5,-1);
    So, va_start initialisiert ap, dazu wird der letzte benannte Parameter der Funktion benötigt.
    arg hat eine Adresse, und alle Parameter, die noch folgen werden liegen geordnet im Speicherbereich dahinter.
    Das va_arg Makro setzt jetzt immer den Zeiger (ap) weiter.Wieviel?Na,das wird durch den 2.Parameter bestimmt, der ein Datentyp ist (int,char*,...), die Größe gibt an,wieweit der Zeiger inkrementiert werden muß.
    Und damit hangeln wir uns durch den Speicher.

  5. #5
    Registrierter Benutzer
    Registriert seit
    05.06.2003
    Beiträge
    118
    Hallo!

    Danke erstmal für deine Erläuterungen. Ich fürchte, ich habe immer noch nicht so genau klargemacht, wo ich den Knackpunkt sehe.

    Hier in deinem Beispiel ist zur compilezeit die Anzahl der Parameter bekannt, somit kann der Parser diesen Punkt durch einen Aufruf einer 5-parametrigen Funktion ersetzen. Schwierig wird dies erst, wenn ich diese Funktion über ein externes Interface (DLL o.Ä.) anspreche, da dort der Aufgerufene nicht mitgegeben bekommt und der Compiler des Aufrufers zwar vorher rein theoretisch den Aufruf in einen 5-parametrigen verwandeln könnte, dieser jedoch beim Aufgerufenen nicht zwangsläufig existieren muß.

    Dein Beispiel scheint mir jedoch auch dafür nicht geeignet: Wie gebe ich denn eine -1 aus? Eine Abschlußkonvention der Parameterliste klappt doch nur, wenn zur Compilezeit bekannt ist, wie der Aufruf aussieht.

    AD!

  6. #6
    Registrierter Benutzer
    Registriert seit
    24.06.2003
    Beiträge
    486
    Original geschrieben von Thomas Engelke

    Hier in deinem Beispiel ist zur compilezeit die Anzahl der Parameter bekannt,
    Die Funktion selber wird einmal kompiliert, und wenn ich die 100 mal mit 100 verschiedenen Parameteranzahlen aufrufe.
    somit kann der Parser diesen Punkt durch einen Aufruf einer 5-parametrigen Funktion ersetzen.
    Welche 5-parametrige Funktion, es gibt keine.Es gibt nur eine, die eine beliebige Anzahl Parameter erwartet, und die wird aufgerufen.
    Schwierig wird dies erst, wenn ich diese Funktion über ein externes Interface (DLL o.Ä.) anspreche, da dort der Aufgerufene nicht mitgegeben bekommt und der Compiler des Aufrufers zwar vorher rein theoretisch den Aufruf in einen 5-parametrigen verwandeln könnte, dieser jedoch beim Aufgerufenen nicht zwangsläufig existieren muß.
    Ok. probieren wir es mit einer anderen Erklärung.
    Du willst ein Array ausgeben, von int.Dummerweise erwartet die Funktion char*.Du kennst die Anzahl.
    Code:
    void printInt(char *p,int anzahl)
    {
    	while(anzahl--)
    	{
    		printf("%d,",*(int*)p);
    		p += sizeof(int);
    	}
    }
    ...
    int array[] = {1,2,3,4,5};
    printInt((char*)&array[0],5);
    Du kennst die Anzahl nicht, aber den Abschluß bildet eine -1.
    Code:
    void printInt(char *p)
    {
    	int i;
    	while((i = *(int*)p) != -1)
    	{
    		printf("%d,",i);
    		p += sizeof(int);
    	}
    }
    ...
    int array[] = {1,2,3,4,-1};
    printInt((char*)&array[0]);
    Muß ich die Anzahl der Parameter zur Compilezeit kennen?
    Nein, weder beim ersten noch beim zweiten Bsp.Zb. lege ich das Array erst zur Laufzeit an.
    Ich weiß, daß die Parameter in einem zusammenhängend Speicherbereich liegen,ich weiß wie groß ein Parameter ist, um mich von einem zu nächsten zu bewegen, und einmal kenne ich die Anzahl (ähnlich printf),und einmal haben wir eine Endemarkierung (ähnlich dem printInts von oben).
    Du kannst dir damit auch leicht ein Bsp bauen, daß int,double,char ausgeben kann (ähnlich printf),zb.(Pseudocode)
    Code:
    int printf(const char* str,char *p)
    {
    	/* Parse str */
    	wenn int dann gebe *(*int)p aus und p += sizeof(int)
    	wenn double dann gebe *(double*)p aus und p += sizeof(double)
    	wenn char dann .... p += 1
    	....
    }
    Und jetzt ersetze das char* p durch ein ...,und einen Zeiger auf das nicht vorhandene p bekommst du durch den Aufruf von va_start(p,str);
    Der Compiler kennt die Adresse von str und weiß, das alle anderen Parameter dahinter im Speicher stehen,und er weiß wie weit er den Zeiger str* inkrementieren muß, damit str auf den Anfang von p zeigt.
    Da ist keine Compilermagie dahinter.
    (Nirgendwo im Standard wird festgelegt,daß es so funktionieren muß,wird aber immer? so gemacht).

    Dein Beispiel scheint mir jedoch auch dafür nicht geeignet: Wie gebe ich denn eine -1 aus? Eine Abschlußkonvention der Parameterliste klappt doch nur, wenn zur Compilezeit bekannt ist, wie der Aufruf aussieht.
    All bets are off.
    Ich hab' keine Ahnung was du meinst.

  7. #7
    Registrierter Benutzer
    Registriert seit
    05.06.2003
    Beiträge
    118
    Original geschrieben von wraith
    All bets are off. Ich hab' keine Ahnung was du meinst.
    Hallo nochmals, und danke erneut, daß du dir soviel Aufwand machst.

    Ich habe deine Argumente verstanden und verinnerlicht. In den meisten Fällen wird also der Abschluß entweder durch einen vorher bestimmten Wert signalisiert ((char *)NULL, -1) oder es lassen sich zur Compilezeit die Anzahl der >erwarteten< Argumente erkennen (Parsing des 1. Parameters von printf()).

    Es läuft im Endeffekt darauf hinaus, daß es keine zuverlässige Möglichkeit gibt, dynamische Parameterlisten zu erlauben, ohne manuell zu signalisieren, obwohl das Signal eigentlich nur für alle die Fälle da ist, wo der >Aufrufer< zur Kompilierzeit des Aufgerufenen noch nicht feststeht. Ich habe dann auch eine ähnliche Antwort weiter unten im FAQ gefunden:

    Code:
    15.13:	How can I call a function with an argument list built up at run
    	time?
    A:	There is no guaranteed or portable way to do this.  If you're
    	curious, ask this list's editor, who has a few wacky ideas you
    	could try...
    Irgendwie erledigt sich damit das Problem sozusagen. Danke an alle, die die Gehirnwindungen mitverbogen haben.

    AD!

  8. #8
    Registrierter Benutzer
    Registriert seit
    24.06.2003
    Beiträge
    486
    Original geschrieben von Thomas Engelke
    oder es lassen sich zur Compilezeit die Anzahl der >erwarteten< Argumente erkennen (Parsing des 1. Parameters von printf()).
    Nein, das passiert auch zur Laufzeit (der gcc prüft zwar den String und gibt Warnungen aus,aber das eigentliche Parsen des Strings passiert zur Laufzeit).
    Nimm an,du übergibst einen String,der erst vom User eingegebn wird.
    Es läuft im Endeffekt darauf hinaus, daß es keine zuverlässige Möglichkeit gibt, dynamische Parameterlisten zu erlauben, ohne manuell zu signalisieren, obwohl das Signal eigentlich nur für alle die Fälle da ist, wo der >Aufrufer< zur Kompilierzeit des Aufgerufenen noch nicht feststeht.
    Der Mechanismus wurde ebend erst später zu C hinzugefügt, um printf Aufrufe zuerlauben.
    Sicherlich nicht die beste Möglichkeit,aber sie funktioniert.

  9. #9
    Registrierter Benutzer
    Registriert seit
    05.06.2003
    Beiträge
    118
    Original geschrieben von wraith
    ... Nein, das passiert auch zur Laufzeit (der gcc prüft zwar den String und gibt Warnungen aus,aber das eigentliche Parsen des Strings passiert zur Laufzeit).
    Nimm an,du übergibst einen String,der erst vom User eingegebn wird. ...
    Das sollte imho schwierig werden, da printf() und seine Kollegen einen const char * erwarten.

    AD!

  10. #10
    Registrierter Benutzer
    Registriert seit
    24.06.2003
    Beiträge
    486
    Original geschrieben von Thomas Engelke
    Das sollte imho schwierig werden, da printf() und seine Kollegen einen const char * erwarten.
    Und?Der Cast von char* nach const char* ist implizit.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
    	char *p = malloc(100);
    	int a = 1,b = 2,c = 3;
    	fgets(p,100,stdin);/* gib "%d %d %d" ein,ohne " " */
    
    	printf(p,a,b,c);
    }
    Das const char* sagt dem Aufrufer nur,"Hallo ich verändere den Wert nicht".

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •