Forum: PC-Programmierung C Variadische Funktion mit Array


von Tester (Gast)


Lesenswert?

Hallo,

ich habe beispielsweise eine Funktion wie aus dem Wikipedia-Artikel zu 
variadischen Funktionen:

double mittelwert(int num_zahlen, ...)

Ist es möglich, diese Funktion mit einem Array aus 100 double Variablen 
aufzurufen, ohne jedes Array-Element einzeln übergeben zu müssen?

Bei printf würde ich in dem Fall vprintf verwenden und dann die va_list 
selber aufbauen. Aber wenn es vprintf nicht gäbe, gibt es dann keine 
Möglichkeit mehr außer jedes Element einzeln der Funktion zu übergeben?

von Jochen (Gast)


Lesenswert?

Tester schrieb:
> Bei printf würde ich in dem Fall vprintf verwenden und dann die va_list
> selber aufbauen.
Wie machst du das denn?

von Rolf M. (rmagnus)


Lesenswert?

Tester schrieb:
> Ist es möglich, diese Funktion mit einem Array aus 100 double Variablen
> aufzurufen, ohne jedes Array-Element einzeln übergeben zu müssen?

Nein, zumindest nicht in Standard-C. Wenn du ein Array übergeben willst, 
warum schreibst du dann nicht einfach eine Funktion, die ein Array 
erwartet? Variadische Funktionen sind ja gerade dafür da, dass man die 
Werte als einzelne Parameter ggf. unterschiedlichen Typs übergeben kann. 
Wenn du genau das nicht willst, scheint mir eine variadische Funktion 
der falsche Weg zu sein.

> Bei printf würde ich in dem Fall vprintf verwenden und dann die va_list
> selber aufbauen.

Dazu gibt's aber auch keinen Weg in Standard-C.

von Nop (Gast)


Lesenswert?

Rolf M. schrieb:

> Dazu gibt's aber auch keinen Weg in Standard-C.

Notfalls macht man halt nen LISP-Interpreter in C. ;-)

von Tester (Gast)


Lesenswert?

Jochen schrieb:
> Wie machst du das denn?

Wie va_list aufgebaut ist, ist leider implementierungsabhängig.
Als ganz übler Hack funktioniert zumindest das folgende:
1
void printIntArray(int n, int values[])
2
{
3
    va_list ap;
4
    char fmt[100];
5
    int i;
6
    
7
    if (n > 0 && n < 25) 
8
    {
9
        strcpy(fmt, "%d");
10
        for (i = 0; i < n-1; i++) {
11
            strcat(fmt, ", %d");
12
        }
13
        ap = (va_list)&values[0];
14
        vprintf(fmt, ap);
15
    }
16
    return;
17
}
18
19
20
int main()
21
{
22
    int werte[5];
23
    werte[0] = 111;
24
    werte[1] = 222;
25
    werte[2] = 333;
26
    werte[3] = 444;
27
    werte[4] = 555;
28
29
    printIntArray(5, werte);
30
31
    return 0;
32
}

von Tester (Gast)


Lesenswert?

Rolf M. schrieb:
> Nein, zumindest nicht in Standard-C. Wenn du ein Array übergeben willst,
> warum schreibst du dann nicht einfach eine Funktion, die ein Array
> erwartet? Variadische Funktionen sind ja gerade dafür da, dass man die
> Werte als einzelne Parameter ggf. unterschiedlichen Typs übergeben kann.
> Wenn du genau das nicht willst, scheint mir eine variadische Funktion
> der falsche Weg zu sein.

Ich verwende eine externe Bibliothek die nur eine solche Funktion zur 
Verfügung stellt. Die Funktion darf auch nur einmal mit vollen 
Parametern aufgerufen werden, weil damit intern eine Kommunikation 
angestoßen wird. Ein Funktionsaufruf entspricht später einem Telegramm. 
Wenn ich jetzt für mein Array die Funktion mehrfach aufrufe, dann gibt 
es mehrere Telegramme. Und das gilt es zu vermeiden.

von Jim M. (turboj)


Lesenswert?

Tester schrieb:
> Ich verwende eine externe Bibliothek die nur eine solche Funktion zur
> Verfügung stellt. Die Funktion darf auch nur einmal mit vollen
> Parametern aufgerufen werden, weil damit intern eine Kommunikation
> angestoßen wird. Ein Funktionsaufruf entspricht später einem Telegramm.
> Wenn ich jetzt für mein Array die Funktion mehrfach aufrufe, dann gibt
> es mehrere Telegramme. Und das gilt es zu vermeiden.

In dem Falle könnte man versuchen den Source Code der Lib zu bekommen 
oder eine besser geeignete zu finden.

Bei überschaubarem Wertebereich für num_zahlen könnte man einen 
Codegenerator schreiben oder den (Turing-vollständigen) Präprozessor auf 
eine clevere Art nutzen um die Array Zugriffe zu generieren.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Tester schrieb:
> Ich verwende eine externe Bibliothek die nur eine solche Funktion zur
> Verfügung stellt.

Und woher weiß die Funktion, was Du ihr übergibst?

Bei printf & Co. ist das durch den Format-String geklärt, aber Deine 
Funktion erhält so etwas nicht.

Soll das einfach nur eine Funktion sein, der eine unterschiedliche 
Anzahl von Werten des gleichen Typs übergeben wird?

Das ist kaputt. Dafür sind variadische Funktionen weder geschaffen 
worden noch überhaupt erforderlich; so etwas kann man mit exakt zwei 
Argumenten erledigen: Anzahl der Elemente und Adresse des ersten 
Elements.

Was soll das für eine "externe Bibliothek" sein? Was für Leute haben das 
geschrieben?

von Tester (Gast)


Lesenswert?

Doch, die Funktion besitzt einen Formatstring wie printf.
Also func(char *fmt, ...).
Die Funktion stammt von Siemens ;-)

Es geht mir um eine mögliche Lösung: möglich ja oder nein. Und nicht ob 
dieser Hersteller Mist ist, das kann ich mir nicht aussuchen und komme 
auch an keine Internas heran.

Wie ich gezeigt habe ich es bei vprintf möglich. Zumindest wenn va_list 
so implementiert ist, dass es auf das erste Element der Parameterliste 
zeigt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wenn es nur die variadische Funktion selbst gibt, und nicht eine auf 
va_arg aufbauende Variante davon, dann sehe ich keine (saubere) Chance.

Schließlich musst Du den Compiler dazu bringen, die gewünschten 
Argumente auf den Stack zu packen, und das wirst Du nur mit wüstem 
nichtportablen Assembler-Foo hinbekommen.

von A. S. (Gast)


Lesenswert?

Dann bringe doch mal ein Beispiel der Funktion. Du kannst ja die 
Bezeichner ändern.

Ansonsten kannst du die argumente.auch mit memcpy auf den Stack 
kopieren, wenn sie geordnet vorliegen. Der Code wäre aber wenig 
portabel.

von Tester (Gast)


Lesenswert?

Wenn Integer-Werte geschrieben werden sollen, sieht die Funktion 
beispielsweise so aus:
SetTagMultiWait("%d%d", nameTag1, wertTag1, nameTag2, wertTag2);

Der C-Compiler generiert P-Code und läuft dann in einem Interpreter, 
also nichts was man sonst so kennt. Erstaunlicherweise ist das aber 
recht standardkonform umgesetzt.

Wir können uns auch einfach auf eine Umsetzung mit printf() einigen, die 
Problemstellung ist ja identisch.

von zer0 (Gast)


Lesenswert?


von Theor (Gast)


Lesenswert?

Tja. Was soll man da sagen, als "Siemens". :-)

von asdfasd (Gast)


Lesenswert?

> Ist es möglich, diese Funktion mit einem Array aus 100 double Variablen
> aufzurufen, ohne jedes Array-Element einzeln übergeben zu müssen?

Übergeben musst du es, du kannst dir aber etwas Schreibarbeit sparen:
1
#define EXP1(id,i)      id[(i)]
2
#define EXP2(id,i)      EXP1(id,(i)), EXP1(id,(i)+1)
3
#define EXP4(id,i)      EXP2(id,(i)), EXP2(id,(i)+2)
4
#define EXP8(id,i)      EXP4(id,(i)), EXP4(id,(i)+4)
5
#define EXP16(id,i)     EXP8(id,(i)), EXP8(id,(i)+8)
6
#define EXP32(id,i)     EXP16(id,(i)), EXP16(id,(i)+16)
7
#define EXP64(id,i)     EXP32(id,(i)), EXP32(id,(i)+32)
8
9
#define EXP100(id,i)    EXP64(id,(i)), EXP32(id,(i)+64), EXP4(id,(i)+64+32)
10
11
...
12
    mittelwert(100, EXP100(foo,0));  // übergibt foo[0], ..., foo[99]
13
...

Das gibt's auch als generische Lösung, ist aber sehr komplex.

Beitrag #5137400 wurde von einem Moderator gelöscht.
von asdfasd (Gast)


Lesenswert?

Vergessen: wenn man auf solche Probleme trifft, sollte man seinen Ansatz 
überdenken - meist gibt es Gründe dafür (hier z.B. dass evtl gar nicht 
soviel Platz im Telegramm ist, um mehr als eine Handvoll Parameter 
anzugeben und der Libraryersteller deshalb nur diese Variante anbietet).

von A. S. (Gast)


Lesenswert?

Bei printf geht memcpy.
Wenn die Parameter nicht wohlgeordnet vorliegen, musst Du per Hand 
kopieren. Das macht natürlich nur Sinn, wenn die Anzahl zu Laufzeit 
variabel ist (an dieser Stelle)

Ob Dein Interpreter damit klarkommen, ....?

von asdfasd (Gast)


Lesenswert?

Memcpy und ähnliche Tricks funktionieren nur in Ausnahmefällen. 
Komplexere Architekturen haben nicht unbedingt alle Parameter in der 
angegeben Reihenfolge auf dem Stack; und es hat nichts damit zu tun, ob 
die Funktion printf oder mittelwert heißt ...

von Tester (Gast)


Lesenswert?

Kann jemand mal ein Beispiel (x86) für die Variante mit memcpy und 
printf geben?

int intWert = 123;
printf("%d%d", intWert);

In dem Fall würde printf versuchen auf einen Wert hinter esp 
zuzugreifen. Dort müsste ich dann per memcpy meine Werte ablegen. Aber 
wie komme ich aus reinem C an die Adresse von esp?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Der einzig sinnvolle Weg erscheint mir Assembler — falls die Umgebung 
dies unterstützt.

Mit GCC würde es so aussehen:  Auf C-Ebene gibt es einen Prototypen der 
in Assembler implementierten Funktion.  In Asm wird dann eine globale 
Funktion implementiert, welche die Argumente des Callers entgegennimmt 
und so aufbereitet (in Registern, auf dem Stack, Frame-Pointer 
initializieren falls benötigt, gegebenenfalls Register sichern und 
widerherstellen, Stack-Alignment beachten, etc.) dass das C-ABI und das 
Interface der aufzurufenden Funktion beachtet werden.  Den Format-String 
kann man in C erstellen.

Das in "reinem C" zu implementieren geht nicht; evtl. kannst du was 
hacken das mehr oder weniger stabil ist und für deine ABI, 
Anwendungsfall und Compiler funktioniert — aber immer ohne Garantie, 
weil du nie weißt, was der C-Compiler genau macht.

I.W. gibt es 2 Arten, hier ein Array zu übergeben: Als Adresse des 
ersten Elements, oder indem man das Array in eine Struktur packt und die 
Struktur übergibt (per Wert oder per Adresse).  Struktur geht natürlich 
nur, wenn die Größe — oder zumindest deren Obergrenze — zu Compilezeit 
bekannt ist.  Die Größe des Arrays wird als Anzahl der Elemente 
übergeben oder indem nach dem letzten Element ein weiteres zur 
Ende-Markierung übergeben wird (analog zu Strings).

Zusätzlich setzt dies natürlich voraus, dass es entsprechende Formate 
gibt, um dies im Format-String zu beschreiben.

von Heiko L. (zer0)


Lesenswert?

Johann L. schrieb:
> Der einzig sinnvolle Weg erscheint mir Assembler — falls die Umgebung
> dies unterstützt.

https://github.com/libffi/libffi

von A. S. (Gast)


Lesenswert?

Wie gesagt, der Code wäre nicht portabel und verlünge eine Menge an 
Absicherung und zumindest die Compilereinstellung, dass keine Variablen 
in Registern übergeben werden, etc. pp. , aber prinzipiell:
1
int *a(int p, int q, int r)
2
{
3
    return &p;
4
}
5
int *b(void)
6
{
7
int p;
8
9
    return &p;
10
}

Die Funktion a gibt die Adresse des ersten Arguments zurück. Bei b ist 
es die der ersten lokalen Variablen.

wenn also a oder b die gleiche Signatur haben und Du sie vorher 
aufrufst, dann ist dies ein Indiz dafür, wo die Funktion die Parameter 
erwartet.

Also a (oder b) aufrufen und dann die Daten dorthin kopieren.

Meist gibt es aber in C (der Code ist eh nicht portabel) entsprechende 
Token zur Ermittlung der Stackadresse.

von Noch einer (Gast)


Lesenswert?

> #define EXP1(id,i)      id[(i)] ... ... ...

Das ist zumindest ein vernünftiger Kompromiss aus Aufwand und 
Nachvollziehbarkeit. Denk an deinen armen Nachfolger, der dein Programm 
in 10 Jahren ändern muss.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Achim S. schrieb:
> int *a(int p, int q, int r)
> {
>     return &p;
> }
>
> int *b(void)
> {
>    int p;
>    return &p;
> }
>
> Die Funktion a gibt ...

Undefined Behaviour falls man einen so erhaltenen Zeiger außerhalb von a 
dereferenziert.  Dito für b.
1
C99 Annex J.2 Undefined behavior
2
3
The behavior is undefined in the following circumstances:
4
...
5
— An object is referred to outside of its lifetime (6.2.4).
6
...

Und Undefined Behaviour ist in C99 §3.4.3 erklärt:
1
undefined behavior
2
3
behavior, upon use of a nonportable or erroneous program construct
4
or of erroneous data, for which this International Standard imposes
5
no requirements.
6
7
NOTE: Possible undefined behavior ranges from ignoring the situation
8
completely with unpredictable results, to behaving during translation
9
or program execution in a documented manner characteristic of the
10
environment (with or without the issuance of a diagnostic message),
11
to terminating a translation or execution (with the issuance of a
12
diagnostic message).

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Ginge auch C++11, gibt es einen Compiler dafür für diese Plattform? Da 
gäbe es vermutlich eine einigermaßen komfortable, korrekte 
(wohldefinierte) Möglichkeit, sofern die Anzahl der Array Elemente zur 
Compilezeit bekannt ist.

von asdfasd (Gast)


Lesenswert?

Es geht um einen C-Compiler für eine undokumentierte und ungewöhnliche 
Architektur (P-Code mit Interpreter). Wer da glaubt, mit irgendwelchen 
Speichertrickserei was zu erreichen, ...

Wenn du erst zur Laufzeit die Anzahl Parameter weißt, mach dir zu Nutze, 
dass eine Vararg-Funktion auch mit mehr als den benötigten Parametern 
aufgerufen werden kann: übergieb immer die Maximalzahl und pass nur den 
"Formatstring" entsprechend an.

Z.Bsp. so:
1
double
2
mittelwert_a(int n, double a[])
3
{
4
   double t[100];
5
   assert(n >= 0 && n <= 100);
6
   for (int i=0; i<100; ++i)
7
       t[i] = i<n ? a[i] : 0;
8
   return mittelwert(n, EXP100(t, 0)); // EXP100 s.o.
9
}


PS: Würd mich nicht wundern, wenn der Compiler selbst ein Limit für die 
maximale Anzahl an Parametern hat ...

von Tester (Gast)


Lesenswert?

Ich werde versuchen etwas mit der vorgeschlagenen Makrologie zu bauen, 
da mir die anderen Lösungen auch zu unsicher in ihrer Funktion sind.

Trotzdem ist es interessant wie man es machen könnte, bzw. wo die 
Grenzen sind.

von asdfasd (Gast)


Lesenswert?

Nun, die Grenzen setzt der C-Standard, und der bietet nichts, um eine 
Vararg-Funktion mit einer dynamischen Anzahl Parametern aufzurufen. Und 
da die Architekturen gerade hier sehr unterschiedlich sind, lohnen sich 
Tricksereien selten - meist ist es einfacher, ne kleine Assemblerroutine 
zu schreiben oder sich halt mit den Gegebenheit abzufinden.

Leider ist der C-Standard teilweise recht komplex, was es einem nicht 
unbedingt einfacher macht. So bin ich mir z.B. bei der folgenden 
Variante aus der Schmuddelecke nicht sicher, ob sie Standardkonform ist 
(zumindest scheint es verschiedentlich Implementationsprobleme zu 
geben):
1
double
2
mittelwert_a(int n, double a[])
3
{
4
   struct { double a[100]; } t;
5
   assert(n >= 0 && n <= 100);
6
   for (int i=0; i<100; ++i)
7
       t.a[i] = i<n ? a[i] : 0;
8
   return mittelwert(n, t); // expand t.a by passing t as a struct
9
}

von A. S. (Gast)


Lesenswert?

Johann L. schrieb:
> Undefined Behaviour falls ...

Ja, natürlich. Deshalb schrieb ich ja auch "nicht portabel".

Wäre es implementation defined, wäre (in den allermeisten Fällen) auch 
ein portables Konstrukt möglich.

Wobei viele von uns "Undefined"-Konstrukte ohne große Sorgen verwenden, 
z.B. den Überlauf eines signed-Integers. Einfach, weil kaum jemand von 
uns jemals mit Architekturen in Berührung kam, bei denen es nach 
Differenzbildung wieder passt.

von Dr. Sommer (Gast)


Lesenswert?

Achim S. schrieb:
> Wobei viele von uns "Undefined"-Konstrukte ohne große Sorgen verwenden,
> z.B. den Überlauf eines signed-Integers.
m.W. kann das sogar der GCC für x86 kaputtoptimieren, denn der wirft 
Konstrukte dieser Art komplett raus:
1
int x = ...;
2
if (x + 1 < 0) { // Überlauf-Check
3
}
Was schon zu viel Freude geführt hat

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.