Ich habe gelesen, dass das Konvertieren eines Funktionszeigers in einen Datenzeiger und umgekehrt auf den meisten Plattformen funktioniert, aber nicht garantiert funktioniert. Warum ist das so? Sollten nicht beide einfach Adressen im Hauptspeicher sein und daher kompatibel sein?
c++
c
pointers
function-pointers
Gexizid
quelle
quelle
void
. Die Konvertierung eines Funktionszeigers invoid *
darf die Darstellung nicht verändern. Einvoid *
Wert, der sich aus einer solchen Konvertierung ergibt, kann ohne Informationsverlust mithilfe einer expliziten Umwandlung wieder in den ursprünglichen Funktionszeigertyp konvertiert werden. Hinweis : Der ISO C-Standard verlangt dies nicht, ist jedoch für die POSIX-Konformität erforderlich.dlsym()
- beachten Sie das Ende des Abschnitts 'Anwendungsnutzung', in dem es heißt: Beachten Sie, dass die Konvertierung von einemvoid *
Zeiger in einen Funktionszeiger wie in:fptr = (int (*)(int))dlsym(handle, "my_function");
nicht durch den ISO C-Standard definiert ist. Dieser Standard erfordert, dass diese Konvertierung bei konformen Implementierungen ordnungsgemäß funktioniert.Antworten:
Eine Architektur muss Code und Daten nicht im selben Speicher speichern. Bei einer Harvard-Architektur werden Code und Daten in einem völlig anderen Speicher gespeichert. Die meisten Architekturen sind Von Neumann-Architekturen mit Code und Daten im selben Speicher, aber C beschränkt sich nicht nur auf bestimmte Arten von Architekturen, wenn dies überhaupt möglich ist.
quelle
CS != DS
).VirtualProtect
, um Datenbereiche als ausführbar zu markieren.Einige Computer haben (hatten) separate Adressräume für Code und Daten. Auf solcher Hardware funktioniert es einfach nicht.
Die Sprache wurde nicht nur für aktuelle Desktop-Anwendungen entwickelt, sondern ermöglicht auch die Implementierung auf einer großen Menge von Hardware.
Es scheint, als hätte das C-Sprachkomitee nie beabsichtigt
void*
, ein Zeiger auf die Funktion zu sein, sondern nur einen generischen Zeiger auf Objekte.Die C99-Begründung lautet:
Hinweis Über Zeiger auf Funktionen im letzten Absatz wird nichts gesagt . Sie können sich von anderen Hinweisen unterscheiden, und das ist sich des Ausschusses bewusst.
quelle
void *
.sizeof(void*) == sizeof( void(*)() )
würde jedoch Platz verschwenden, wenn Funktionszeiger und Datenzeiger unterschiedliche Größen haben. Dies war ein häufiger Fall in den 80er Jahren, als der erste C-Standard geschrieben wurde.Für diejenigen, die sich an MS-DOS, Windows 3.1 und älter erinnern, ist die Antwort recht einfach. All dies unterstützte mehrere verschiedene Speichermodelle mit unterschiedlichen Merkmalskombinationen für Code- und Datenzeiger.
So zum Beispiel für das Compact-Modell (kleiner Code, große Datenmengen):
und umgekehrt im mittleren Modell (großer Code, kleine Daten):
In diesem Fall hatten Sie keinen separaten Speicher für Code und Datum, konnten jedoch nicht zwischen den beiden Zeigern konvertieren (ohne nicht standardmäßige Modifikatoren __near und __far).
Darüber hinaus gibt es keine Garantie dafür, dass die Zeiger, selbst wenn sie dieselbe Größe haben, auf dasselbe verweisen - im DOS Small-Speichermodell werden sowohl Code als auch Daten in der Nähe von Zeigern verwendet, sie zeigen jedoch auf unterschiedliche Segmente. Wenn Sie also einen Funktionszeiger in einen Datenzeiger konvertieren, erhalten Sie keinen Zeiger, der überhaupt eine Beziehung zur Funktion hat, und daher war eine solche Konvertierung nicht sinnvoll.
quelle
int*
in einvoid*
konvertieren, erhalten Sie einen Zeiger, mit dem Sie eigentlich nichts anfangen können, aber es ist trotzdem nützlich, die Konvertierung durchführen zu können. (Dies liegt daranvoid*
, dass jeder Objektzeiger gespeichert werden kann und daher für generische Algorithmen verwendet werden kann, die nicht wissen müssen, welchen Typ sie enthalten. Dasselbe könnte auch für Funktionszeiger nützlich sein, wenn dies zulässig wäre.)int *
zuvoid *
, dievoid *
auf dasselbe Objekt zumindest Punkt gewährleistet ist wie das Originalint *
hat - so ist dies nützlich für die generischen Algorithmen , dass der Zugang des spitzen-zu - Objekt, wieint n; memcpy(&n, src, sizeof n);
. In dem Fall, in dem das Konvertieren eines Funktionszeigers in avoid *
keinen Zeiger ergibt, der auf die Funktion zeigt, ist dies für solche Algorithmen nicht sinnvoll. Das einzige, was Sie tun können, ist, dasvoid *
Zurück erneut in einen Funktionszeiger zu konvertieren Verwenden Sie einfach einen Zeigerunion
mit einemvoid *
und einer Funktion.void*
sie auf die Funktion hinweisen würdenmemcpy
. :-Pvoid
. Die Konvertierung eines Funktionszeigers invoid *
darf die Darstellung nicht verändern. Einvoid *
Wert, der sich aus einer solchen Konvertierung ergibt, kann ohne Informationsverlust mithilfe einer expliziten Umwandlung wieder in den ursprünglichen Funktionszeigertyp konvertiert werden. Hinweis : Der ISO C-Standard verlangt dies nicht, ist jedoch für die POSIX-Konformität erforderlich.Zeiger auf ungültig sollen einen Zeiger auf jede Art von Daten aufnehmen können - aber nicht unbedingt einen Zeiger auf eine Funktion. Einige Systeme haben andere Anforderungen an Zeiger auf Funktionen als Zeiger auf Daten (z. B. gibt es DSPs mit unterschiedlicher Adressierung für Daten gegenüber Code, mittleres Modell unter MS-DOS verwendet 32-Bit-Zeiger für Code, aber nur 16-Bit-Zeiger für Daten). .
quelle
Zusätzlich zu dem, was hier bereits gesagt wurde, ist es interessant, sich POSIX anzusehen
dlsym()
:quelle
void*
und zurück übertragen werden können.void*
mit einem Funktionszeiger kompatibel sein, POSIX dagegen.C ++ 11 hat eine Lösung für die seit langem bestehende Nichtübereinstimmung zwischen C / C ++ und POSIX in Bezug auf
dlsym()
. Man kannreinterpret_cast
einen Funktionszeiger in / von einem Datenzeiger konvertieren, solange die Implementierung diese Funktion unterstützt.Aus dem Standard, 5.2.10 Abs. 8, "Das Konvertieren eines Funktionszeigers in einen Objektzeigertyp oder umgekehrt wird bedingt unterstützt." 1.3.5 definiert "bedingt unterstützt" als "Programmkonstrukt, für dessen Unterstützung keine Implementierung erforderlich ist".
quelle
-Werror
). Eine bessere (und nicht UB) Lösung besteht darin, einen Zeiger auf das vondlsym
(dhvoid**
) zurückgegebene Objekt abzurufen und diesen in einen Zeiger in einen Funktionszeiger umzuwandeln . Immer noch implementierungsdefiniert, aber keine Ursache für eine Warnung / einen Fehler mehr .dlsym
undGetProcAddress
zu kompilieren.-pedantic
) nicht warnen. Auch hier ist keine Spekulation möglich.Abhängig von der Zielarchitektur können Code und Daten in grundlegend inkompatiblen, physikalisch unterschiedlichen Speicherbereichen gespeichert werden.
quelle
void *
ist groß genug, um einen Datenzeiger aufzunehmen, aber nicht unbedingt einen Funktionszeiger.undefined bedeutet nicht unbedingt nicht erlaubt, es kann bedeuten, dass der Compiler-Implementierer mehr Freiheit hat, es so zu machen, wie er will.
Beispielsweise ist dies bei einigen Architekturen möglicherweise nicht möglich. Undefiniert ermöglicht es ihnen, weiterhin über eine konforme C-Bibliothek zu verfügen, auch wenn Sie dies nicht tun können.
quelle
Eine andere Lösung:
Unter der Annahme, dass POSIX garantiert, dass Funktions- und Datenzeiger dieselbe Größe und Darstellung haben (ich kann den Text dafür nicht finden, aber das angeführte Beispiel-OP legt nahe, dass sie zumindest beabsichtigen , diese Anforderung zu erfüllen), sollte Folgendes funktionieren:
Dadurch wird vermieden, dass die Aliasing-Regeln verletzt werden, indem die
char []
Darstellung durchlaufen wird , mit der alle Typen aliasisiert werden dürfen.Noch ein Ansatz:
Aber ich würde den
memcpy
Ansatz empfehlen, wenn Sie absolut 100% korrektes C wollen.quelle
Es können verschiedene Typen mit unterschiedlichem Platzbedarf sein. Das Zuweisen zu einem kann den Wert des Zeigers irreversibel aufteilen, sodass das Zurückweisen zu etwas anderem führt.
Ich glaube, es können verschiedene Typen sein, weil der Standard mögliche Implementierungen nicht einschränken möchte, die Platz sparen, wenn sie nicht benötigt werden oder wenn die Größe dazu führen kann, dass die CPU zusätzlichen Mist machen muss, um sie zu verwenden, usw.
quelle
Die einzige wirklich tragbare Lösung besteht darin, sie nicht
dlsym
für Funktionen zu verwenden, sondern stattdessendlsym
einen Zeiger auf Daten zu erhalten, die Funktionszeiger enthalten. Zum Beispiel in Ihrer Bibliothek:und dann in Ihrer Bewerbung:
Im Übrigen ist dies ohnehin eine gute Entwurfspraxis und erleichtert die Unterstützung des dynamischen Ladens über
dlopen
und der statischen Verknüpfung aller Module auf Systemen, die keine dynamische Verknüpfung unterstützen oder bei denen der Benutzer / Systemintegrator keine dynamische Verknüpfung verwenden möchte.quelle
foo_module
Struktur hat (mit eindeutigen Namen), können Sie einfach eine zusätzliche Datei mit einem Array vonstruct { const char *module_name; const struct module *module_funcs; }
und einer einfachen Funktion erstellen , um diese Tabelle nach dem Modul zu durchsuchen, das Sie "laden" und den richtigen Zeiger zurückgeben möchten, und diese dann verwenden anstelle vondlopen
unddlsym
.Ein modernes Beispiel dafür, wo sich Funktionszeiger in der Größe von Datenzeigern unterscheiden können: C ++ - Klassenmitgliedsfunktionszeiger
Direkt zitiert von https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: Bei Verwendung der Mehrfachvererbung kann ein Zeiger auf eine Mitgliedsfunktion (abhängig von Compiler, Version, Architektur usw.) tatsächlich als gespeichert werden
das ist offensichtlich größer als a
void *
.quelle
Auf den meisten Architekturen haben Zeiger auf alle normalen Datentypen dieselbe Darstellung, sodass das Umwandeln zwischen Datenzeigertypen ein No-Op ist.
Es ist jedoch denkbar, dass Funktionszeiger eine andere Darstellung erfordern, möglicherweise sind sie größer als andere Zeiger. Wenn void * Funktionszeiger enthalten könnte, würde dies bedeuten, dass die Darstellung von void * größer sein müsste. Und alle Abgüsse von Datenzeigern auf / von void * müssten diese zusätzliche Kopie ausführen.
Wie bereits erwähnt, können Sie dies bei Bedarf mithilfe einer Gewerkschaft erreichen. Die meisten Verwendungen von void * beziehen sich jedoch nur auf Daten. Daher wäre es mühsam, die gesamte Speichernutzung zu erhöhen, falls ein Funktionszeiger gespeichert werden muss.
quelle
Ich weiß , dass dies nicht auf seit 2012 kommentiert worden, aber ich dachte , es wäre nützlich , hinzufügen , dass ich tun wissen , eine Architektur , die hat sehr auf dieser Architektur überprüft Privileg , da einen Anruf nicht kompatibel Zeiger für Daten und Funktionen und führt zusätzliche Informationen. Keine Menge Casting wird helfen. Es ist die Mühle .
quelle