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?
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.
Jochen schrieb:> Wie machst du das denn?
Wie va_list aufgebaut ist, ist leider implementierungsabhängig.
Als ganz übler Hack funktioniert zumindest das folgende:
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.
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.
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?
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.
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.
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.
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.
> 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:
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).
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, ....?
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 ...
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?
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.
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(intp,intq,intr)
2
{
3
return&p;
4
}
5
int*b(void)
6
{
7
intp;
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.
> #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.
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
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.
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(intn,doublea[])
3
{
4
doublet[100];
5
assert(n>=0&&n<=100);
6
for(inti=0;i<100;++i)
7
t[i]=i<n?a[i]:0;
8
returnmittelwert(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 ...
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.
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(intn,doublea[])
3
{
4
struct{doublea[100];}t;
5
assert(n>=0&&n<=100);
6
for(inti=0;i<100;++i)
7
t.a[i]=i<n?a[i]:0;
8
returnmittelwert(n,t);// expand t.a by passing t as a struct
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.
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: