Re: komplexes Problem mit Funktionszeigern
- From: Jan Seiffert <nomail@invalid>
- Date: Tue, 19 May 2009 15:43:57 +0200
M. Mertens wrote:
Hallo,Hallo
Nur damit ich das Richtig verstehe:
Die Funktion gibt einen struct "by value" zurueck und bei der
Verwendung von
verschiedenen Compilern knallts?
Die Funktion gibt in bestimmten Fällen ein struct zurück, und in denen
knallt's, wenn Code des Aufrufers und des Aufgerufenen nicht mit dem
gleichen Compiler erstellt sind.
Das es um das hin- und herreichen von structs geht hat ich schon verstanden, der
Kern der Frage ist, ob du einen Pointer auf ein struct zurueck gibts oder die
C-Moeglichkeit ein struct "so" zurueckzugeben.
Also:
struct foo my_func(int a_param, float another_param)
{
...
}
Statt:
struct foo *my_func(int a_param, float another_param)
{
...
}
Es ist damit ein Implementationsdetail.
Zumindest fuer ersteres besteht die Chance das es eine Compileroption
gibt das
umzuschalten. (Warsch. am Borland Compiler, da der Microsoft Compiler
"Hausrecht" hat)
Habe ich auch schon ohne Erfolg nach gesucht, das Stichwort ist (siehe
Beitrag von Just Pronto) wohl structure padding. Wobei ich mir noch
nicht klar bin, wieso dies Implementierungsdetail sein soll.
Padding ist deshalb ein Implementierungsdetail, weil der C-Standard nicht wissen
kann, was fuer eine konkrete Hardware/Implementation notwendig ist (z.B.
alignment damit der Prozessor die Daten lesen kann). Der Standard sagt einfach
nur, das da padding sein kann. Punkt.
Einem 8-Bit Prozessor ist es egal wie ein 32Bit Wert liegt, er kann ihn eh nur
8Bit weise bearbeiten. Ein DSP sieht das sicher anders.
Selbst auf ein und der selben Hardware kann der eine Compilerhersteller zu dem
schluss gekommen sein, das selbst 16Bit Variablen auf 32Bit ausgerichtet werden
mussen, wegen er Performance. Ein anderer Compilerherrsteller hat da keinen
signifikanten Unterschied festgestellt und optimiert lieber auf platz.
Das kann sich schon auch einfach mit Compileroptionen aendern (-O999 vs. -Os).
Man unterlaesst solche extremen spielchen meist nur aus Gruenden der
interoperabilitaet und damit sich Programmierer nicht zu schnell die Fusse
wegschiessen.
Wenn man mal die Situation auf das Wesentliche vereinfacht, sieht das
Ganze z.B. so aus:
MY_TIME getTime(MY_OBJ *p);
Dabei könnte MY_TIME ein struct aus zwei Integern o.ä. und MY_OBJ ein
struct, das ein Objekt repräsentiert, sein.
Das Sympton ist ja, dass der Code von getTime beim Aufgerufenen
ausgeführt wird, jedoch ist das Problem, dass p != p bei Aufrufendem und
Aufgerufenem gilt (nur für den Fall, dass die Rückgabe kein elementarer
Datentyp ist).
Genau das ist was ich mit ABI meinte und Rainer mit Aufrufkonventionen beschrieb.
<Disclaimer>
Das folgend gesagte verlaesst den Rahmen des C-Standards
</Disclaimer>
C schreibt eine menge Sachen expliziet nicht in den Standard, damit es auch auf
"komischen" Plattformen laeuft (z.B. Arizona Microchip PIC uC, Texas Instruments
TMS320C5x DSPs: 8-Level Hardware Stack, kommt man nicht ran, kann man ausser mit
call und ret nicht Manipulieren, wie will man da Parameter ueber den Stack
uebergeben wenn es im Standard stehen wuerde?)
Wenn man C konkret auf einer Plattform implementiert, so hat man sich zu
ueberlegen wie gewisse C-Features "ausgefuehrt" werden.
Der C-Standard laesst einem dafuer Raum.
Auch grade um Sachen zu "emulieren" die die Hardware nicht hergibt
(Funktionszeiger z.B.).
Deshalb sind auch Datentypen so schwammig.
Entweder man macht das in einem "luftlehren Raum" und hat dabei ausser den
Faehigkeiten der CPU nichts einzubeziehen (nackte MicroController, freestanding
Implementation z.B.).
Oder man hat auch andere "Mitspieler" (Hersteller des OS, Hersteller der LibC,
etc.) mit einzubeziehen (Es koennen auch direkte Konkurenten sein, man will z.B.
mit ihnen Kompatibel sein).
Falls man mehrere moeglichkeiten hat etwas zu Implementieren, mochte man
vielleicht die performanteste waehlen, die einfachste, oder die zunkunftssicherste.
Man kann Argumente fuer eine Funktion z.B auf dem Stack uebergeben. Aber da muss
man sich ueber Reihenfolge und padding einig sein, wer was wegraeumt (cdecl,
stdcall sagen dir als Win-Programmierer vielleicht was), wem die Argumente
gehoeren (clobbering).
Man kann Argumente aber auch in Registern hereinreichen (auf vielen
RISC-Maschinen ueblich). Da es aber keine Obergrenze fuer die Anzahl der
Argumente gibt muss man sich einen fallback ueberlegen (z.B. ersten 8 Argumente
in r1-r9, danach in der-und-der Reihenfolge auf Stack). Auch hierbei muessen
sich die Beteiligten einig seien, in welcher Reihenfolge Argumente in Registern
liegen (low-to-high, high-to-low, auf AIX ist es anders als auf Linux obwohl
beides auf PowerPC laeuft, etc.).
Fuer den Rueckgabewert ist es das gleiche Spiel. Wie er zurueck kommt sollten
sich alle Beteiligten einig sein. Man koennte ihn an eine statische
Speicheradresse legen, von der er vom Aufrufer abgeholt werden kann, oder der
Aufrufer muss im Call-Frame Platz fuer den Parameter lassen.
Tja, oder da C ja nur einen Rueckgabewert erlaubt (keine Tuple oder sowas) ist
das schnellste das 1 Register (und das haben die meisten Maschinen) fuer den
Rueckgabewert benutzt wird.
Was meistens gemacht wird. Auf x86 ueberlicherweise EAX.
Das funktioniert natuerlich nur fuer "Basis Typen" die in ein Register passen.
Die C-Moeglichkeit Strukturen "by-value" an Funktionen zu uebergeben sorgt bei
Reg-Call-ABIs auch manchmal fuer Probleme (weil sich die Beteiligten nicht ueber
die expansion einig sind).
Macht man es ueber einen Pointer, hat man wieder einen Basistype, der in ein
Register passt.
By-value muss sich der Compiler ueberlegen wie er es macht. Eine statische
Speicherstelle, Platz im Callframe. Oder mehr Register nehmen, geht natuerlich
nur solange das struct in die Register passt.
Normalerweise sollte soetwas von der Plattform ABI geregelt werden. Aber grade
in diesem Randbereich von Strukturen by Value rumreichen gibt es immer wieder
Probleme. Es ist oft in der ABI einfach nicht beschrieben (vergessen, nicht als
wichtig erachtet, man konnte sich auf keine gemeinsame Loesung einigen, nur
reinreichen geregelt aber nicht rueckgabe).
Ergebniss:
Ein Compiler "ersetzt" sowas immer durch einen "unsichtbaren" Pointer am Anfang
der Parameterliste.
Der naechste kopiert das Eiskalt irgendwo auf den Stack weil er da Platz erwartet.
Ein anderer packt es in Register wenn das Struct weniger als 3 Elemente hat,
sonst macht er was anderes.
Der nachste packt die ersten 3 Elemente _immer_ in Register und wenn das struct
groesser ist in ein 4tes Register einen Pointer auf das ganze struct.
Einiges davon laesst sich sogar mischen, funktioniert per zufall.
Babylonische Sprachverwirrung auf Register Ebene.
In genau das bist du reingelaufen.
Darum sollst du dir mal den erzeugten Maschinencode ansehen (in einem
vereinfachten Beispiel), was die Compiler da eigentlich machen. Was sie da erwarten.
Und dann eben nach Optionen an den Compilern suchen die genau die Call-ABI
beeinflussen. Oder eben das feine Detail wie structs rumgereicht werden.
Und es kann eben gut sein das der Borland Compiler einen
"-pass-structs-like-msvc" Schalter hat. Oder vielleicht hat der MSVC ein
"/whatever" fuer turbo pascal-call womit sie aber in wirklichkeit meinen wie
Borland schon immer am liebsten structs rumgereicht hat (aber vielleicht auch
alles andere verstellt, so das man es nicht benutzen kann).
Frag mich nicht, ich besitze keines dieser Produkte.
RTM
Und stell dich seelisch schon mal drauf ein das du das vielleicht nicht
umstellen kannst mit gegebenen Produkt(version)en.
Aber wieso ist die Codeadresse von getTime noch korrekt, während das
Funktionsargument p nicht mehr korrekt ist? Und wieso liegt das an der
Rückgabe?
Die Codeadresse hat doch damit nichts zu tun... Der Code liegt nunmal an der
Stelle, egal wie er aussieht, was er macht und wie lang er ist, da fangt er an.
Und das p weitergewandert ist, was glaubst du wie das C++ "this" funktioniert?
Mit Feenstaub? Der Compiler reicht es fuer dich als ersten Parameter immer rein.
In deinem Fall hat anscheinend dort einer einen Pointer um das Struct zurueck zu
geben hingetan. Was garnicht mal die duemmste Idee ist wenn man es von Hand
auscoden muss. Auch wenn C immer als portabler Assambler bezeichnet wird: Es
passiert da doch etwas mehr hinter dem Ruecken des Programmieres damit das alles
lauft.
Gruß,Gruss
Martin
Jan
--
Zeit ist, was verhindert, dass alles auf einmal passiert.
John A. Wheelers
.
- References:
- komplexes Problem mit Funktionszeigern
- From: M. Mertens
- Re: komplexes Problem mit Funktionszeigern
- From: Jan Seiffert
- Re: komplexes Problem mit Funktionszeigern
- From: M. Mertens
- komplexes Problem mit Funktionszeigern
- Prev by Date: Re: komplexes Problem mit Funktionszeigern
- Next by Date: Re: komplexes Problem mit Funktionszeigern
- Previous by thread: Re: komplexes Problem mit Funktionszeigern
- Next by thread: Re: komplexes Problem mit Funktionszeigern
- Index(es):
Relevant Pages
|