2010-05-24 3 views
6

Die Situation folgt. Ich habe Bibliothek geteilt, die Klassendefinition enthält -Warum virtuelle C++ - Funktion, die im Header definiert ist, nicht in vtable kompiliert und verknüpft werden kann?

QueueClass : IClassInterface 
{ 
    virtual void LOL() { do some magic} 
} 

My Shared Library initialisieren Klasse Mitglied

QueueClass *globalMember = new QueueClass(); 

Mein Anteil Bibliothek Export C-Funktion, die Zeiger auf globalMember zurück -

void * getGlobalMember(void) { return globalMember;} 

Meine Anwendung verwendet globalMember wie diese

((IClassInterface*)getGlobalMember())->LOL(); 

Nun das sehr Uber Zeug - wenn ich nicht LOL von Shared Library verweisen, dann LOL ist nicht verknüpft und Aufruf von Anwendung löst Ausnahme aus. Grund - VTABLE enthält anstelle des Zeigers auf die Funktion LOL() Null.

Wenn ich LOL() Definition von .h Datei zu .cpp verschiebe, erscheint es plötzlich in VTABLE und alles funktioniert einfach großartig. Was erklärt dieses Verhalten ?! (gcc compiler + ARM architecture_)

+0

Was ist mit anderen nicht-inline virtuellen Funktionen in 'QueueClass' (wenn es welche gibt)? Arbeiten Sie? Mit anderen Worten, ist das Problem mit 'LOL' lokal zu' LOL's Vtable-Eintrag oder ist die gesamte Vtable leer oder fehlt? – AnT

+0

warum privat von IClassInterface erben? –

+2

Warum gibt die 'get ...' Funktion 'void *' anstelle von 'IClassInterface *' zurück? – AnT

Antwort

6

Der Linker ist der Schuldige hier. Wenn eine Funktion inline ist, hat sie mehrere Definitionen, eine in jeder cpp-Datei, auf die verwiesen wird. Wenn Ihr Code niemals auf die Funktion verweist, wird er nie generiert.

Das Layout 10 wird jedoch zur Kompilierungszeit mit der Klassendefinition festgelegt. Der Compiler kann leicht feststellen, dass die LOL() eine virtuelle Funktion ist und einen Eintrag in haben muss.

Wenn es Zeit für die App verbindet, versucht es, alle Werte von QueueClass::_VTABLE zu füllen, findet aber keine Definition von LOL() und lässt es leer (null).

Die Lösung besteht darin, in einer Datei in der gemeinsam genutzten Bibliothek auf LOL() zu verweisen. Etwas so einfaches wie &QueueClass::LOL;. Sie müssen es möglicherweise einer wegwerfbaren Variablen zuweisen, damit der Compiler aufhört, sich über Anweisungen zu beschweren, die keine Wirkung haben.

+0

Das bedeutet der Compiler + Linker sind nur verrückt! Sie dürfen niemals zulassen, dass Sie zur Laufzeit ein lebendes Objekt erstellen, dessen virtuelle Tabelle nicht vollständig definiert ist. In solch einem Fall könnte der Linker einen "nicht aufgelösten" Fehler erzeugen – valdo

+0

Der Compiler optimiert gerade. Wenn du die Funktion nie benutzt, warum sollte sie eine bauen? Wenn der Compiler Code für jede Inline-Funktion für jede Datei ausgibt, wären die Kompilierungszeiten sehr groß, die Verknüpfungszeiten wären ebenfalls sehr groß (der Linker müsste Reems von doppelten nicht referenzierten Funktionen sortieren). –

+0

Das 'nicht aufgelöste' wird normalerweise erzeugt, wenn die Bibliothek geladen wird, nicht wenn es erstellt wird, so dass Sie die Möglichkeit haben, Symbole aus einer anderen Bibliothek zu referenzieren. –

1

Meine Vermutung ist, dass GCC die Gelegenheit nutzt, um den Anruf an LOL zu inline. Ich werde sehen, ob ich eine Referenz für Sie auf diesem finden kann ...

Ich sehe Sechastain Schlag mich zu einer gründlicheren Beschreibung und ich konnte nicht google nach oben die Referenz, die ich suchte. Also werde ich es dabei belassen.

+0

Ja, wenn gcc keine nichtlinearen, virtuellen Funktionen in einer Klasse sieht, wird es nicht die Vtable dafür ausgeben . http://gcc.gnu.org/faq.html # vtables –

+0

Danke, das hilft. Ich erinnere mich nur daran, eine wirklich gute Zusammenfassung zu diesem Thema gelesen zu haben, aber Teh Great Gizoogle versagt mich im Moment, da ich nicht die richtige Kombination von Keywords finden kann, um sie zu finden. Es ist schlimm genug, dass ich die Dinge nicht in meinem Kopf behalten kann, wenn mein virtuelles Gehirn auch undicht ist, was mich in einem ziemlich schlechten Zustand lässt ;-) – Ukko

+0

Whoohoo, ich habe auch mein erstes Neg für eine Antwort mit diesem bekommen ! Ich gebe zu, es war nicht meine beste Arbeit, und für diejenigen, die daran interessiert waren, kam +1, gefolgt von -1, auf +8. Ich habe mich immer gefragt, ob es so funktionieren würde oder ob du das Original +10 verlieren würdest, jetzt wissen wir es ;-) – Ukko

0

Funktionen, die in den Header-Dateien definiert sind, werden bei der Verwendung verwendet. Sie sind nicht als Teil der Bibliothek kompiliert; statt dessen wird der Code der Funktion einfach durch den Code des Aufrufs ersetzt, und das wird kompiliert.

So überrascht es mich nicht zu sehen, dass Sie einen V-Tabelleneintrag nicht finden (was würde es zeigen?), Und ich bin nicht überrascht, dass die Funktionsdefinition in eine CPP-Datei verschieben macht plötzlich die Arbeit. Ich bin ein wenig überrascht, dass das Erstellen einer Instanz des Objekts mit einem Aufruf in der Bibliothek jedoch einen Unterschied macht.

Ich bin mir nicht sicher, ob es eilig ist, aber aus dem Code zur Verfügung gestellt IClassInterface nicht unbedingt LOL, nur QueueClass. Aber Sie übertragen einen IClassInterface-Zeiger auf den LOL-Aufruf.

+3

Das ist eine falsche Argumentation. Nur weil eine Funktion inline ist, bedeutet das nicht, dass Sie ihre Adresse nicht annehmen können. "Worauf es hinweisen würde" ist das Problem des Compilers, nicht das Problem des Benutzers. Normalerweise generieren Compiler einen eigenständigen Rumpf für eine Funktion. Dies hätte es auch in diesem Fall tun sollen. Mit anderen Worten, es gibt keine Probleme mit dem Code/Ansatz selbst. – AnT

+0

Nun, es ist nicht völlig falsch. Wenn du zurück zu C-Tagen gehst und Code in eine Kopfzeile steckst, war das ein totales Rezept für ein Desaster, die Dinge sind jetzt besser; Allerdings gibt es immer noch Probleme mit dem, wo der Compiler diesen Code ausgeben sollte. Unter Ihrem Modell, das eine Header-Datei enthält, wird eine Nicht-Inline-Version von vielen Methoden ausgegeben, was dazu führt, dass riesige Objektdateien und viele Linker ohne Gewinn arbeiten. (Dies ist ein großes Problem, wenn Vorlagen enthalten sind.) Das kommt alles aus dem Grundproblem der dateibasierten, separaten Kompilierung - Kompromisse wurden gemacht, tut mir leid. – Ukko

+0

Ich finde die Weitergabe durch ein 'void *' und Casting um ein Problem, vor allem, wenn die Kette von Cast gegeben: 'QueueClass *' -> 'void *' -> 'IClassInterface *') –

0

Wenn dieses Beispiel vereinfacht ist und Ihr tatsächlicher Vererbungsbaum Mehrfachvererbung verwendet, könnte dies leicht erklärt werden. Wenn Sie für einen Objektzeiger eine Typumwandlung durchführen, muss der Compiler den Zeiger so anpassen, dass auf die richtige vtable verwiesen wird. Da Sie eine void * zurückgeben, verfügt der Compiler nicht über die erforderlichen Informationen, um die Anpassung vorzunehmen.

Edit: Es gibt keinen Standard für C++ Objektlayout, aber für ein Beispiel, wie Mehrfachvererbung funktionieren könnte diesen Artikel von Bjarne Stroustrup sehen sich: http://www-plan.cs.colorado.edu/diwan/class-papers/mi.pdf

Wenn dies tatsächlich Ihr Problem ist, dass Sie vielleicht der Lage, es mit einem einfachen Wechsel zu beheben:

IClassInterface *globalMember = new QueueClass(); 

der C++ Compiler die notwendigen Zeiger Modifikationen tun wird, wenn es die Zuordnung macht, so dass die C-Funktion den richtigen Zeiger zurückgeben kann.

+0

Wie funktioniert die Mehrfachvererbung in echte Welt? Wie werden Vtables erstellt? Say - Klasse multiple: interface1, interface2; Wie wird Vtable nach diesem Monster aussehen ??? Ich bin ziemlich verwirrt. –

+0

@ 0xDEAD BEEF, siehe mein Update. –

2

Ich stimme nicht mit @sechastain.

Inlining ist weit davon entfernt, automatisch zu sein. Unabhängig davon, ob die Methode an Ort und Stelle definiert ist oder ein Hinweis (inline Schlüsselwort oder __forceinline) verwendet wird, entscheidet der Compiler als einziger, ob das Inlining tatsächlich stattfindet, und verwendet dafür komplizierte Heuristiken. Ein besonderer Fall ist jedoch, dass ein Aufruf nicht inline ist, wenn eine virtuelle Methode mit dem Laufzeit-Dispatch aufgerufen wird, gerade weil Runtime-Dispatch und Inlining nicht kompatibel sind.

Um die Genauigkeit der "mit Laufzeit Dispatch" zu verstehen:

IClassInterface* i = /**/; 
i->LOL();     // runtime dispatch 
i->QueueClass::LOL();  // compile time dispatch, inline is possible 

@0xDEAD BEEF: Ich habe Ihr Design spröde finden, gelinde gesagt.

Die Verwendung von C-Casts ist hier falsch:

QueueClass* p = /**/; 
IClassInterface* q = p; 

assert(((void*)p) == ((void*)q)); // may fire or not... 

Grundsätzlich gibt es keine Garantie, dass die 2-Adressen sind gleich: Es ist Implementierung definiert, und es ist unwahrscheinlich Änderung zu widerstehen.

Ich wünschen Sie in der Lage sein, sicher die void* Zeiger auf einen IClassInterface* Zeiger zu werfen, dann müssen Sie es von einer ursprünglich IClassInterface* schaffen, so dass die C++ Compiler die richtigen Zeigerarithmetik auf dem Layout der Objekte in Abhängigkeit führen kann.

Natürlich, ich werde auch unterstreichen als die Verwendung von globalen Variablen ... Sie wissen es wahrscheinlich.

Wie für den Grund der Abwesenheit? Ich sehe ehrlich gesagt nichts außer einem Fehler im Compiler/Linker. Ich habe inline Definition von virtual Funktionen ein paar Mal gesehen (genauer gesagt, die clone Methode) und es hat nie Probleme verursacht.

EDIT: Da "richtige Zeigerarithmetik" wurde hier nicht so gut verstanden, ist ein Beispiel

struct Base1 { char mDum1; }; 

struct Base2 { char mDum2; }; 

struct Derived: Base1, Base2 {}; 

int main(int argc, char* argv[]) 
{ 
    Derived d; 
    Base1* b1 = &d; 
    Base2* b2 = &d; 

    std::cout << "Base1: " << b1 
      << "\nBase2: " << b2 
      << "\nDerived: " << &d << std::endl; 

    return 0; 
} 

Und hier ist, was gedruckt wurde:

Base1: 0x7fbfffee60 
Base2: 0x7fbfffee61 
Derived: 0x7fbfffee60 

nicht der Unterschied zwischen dem

Wert von b2 und &d, obwohl sie sich auf eine Entität beziehen. Dies kann verstanden werden, wenn man an das Speicherlayout des Objekts denkt.

Derived 
Base1  Base2 
+-------+-------+ 
| mDum1 | mDum2 | 
+-------+-------+ 

Wenn man von Derived* zu Base2* Umwandlung wird der Compiler die notwendige Einstellung durchführen (hier inkrementieren die Zeigeradresse um ein Byte), so dass der Zeiger effektiv Zeige endet bis zum Base2 Teil Derived und nicht auf die Base1 Teil fälschlicherweise als Base2 Objekt interpretiert (was wäre böse).

Aus diesem Grund ist die Verwendung von C-Style-Modellen beim Downcasting zu vermeiden. Hier, wenn Sie einen Base2 Zeiger haben, können Sie es nicht als Derived Zeiger neu interpretieren. Stattdessen müssen Sie die static_cast<Derived*>(b2) verwenden, die den Zeiger um ein Byte dekrementiert, so dass es korrekt auf den Anfang des Derived Objekts zeigt.

Das Bearbeiten von Zeigern wird normalerweise als Zeigerarithmetik bezeichnet. Hier führt der Compiler automatisch die richtige Anpassung durch ... unter der Bedingung, dass er den Typ kennt.

Leider kann der Compiler sie beim Konvertieren von void* nicht ausführen, es liegt also am Entwickler, dafür zu sorgen, dass er das richtig handhabt. Die einfache Faustregel ist folgende: T* -> void* -> T* mit dem gleichen Typ auf beiden Seiten erscheinen.

Daher sollten Sie (einfach) Ihren Code korrigieren, indem Sie Folgendes deklarieren: IClassInterface* globalMember und Sie hätten kein Portabilitätsproblem. Sie werden wahrscheinlich immer noch ein Wartungsproblem haben, aber das ist das Problem der Verwendung von C mit OO-Code: C ist sich nicht bewusst, dass irgendwelche objektorientierten Dinge passieren.

+0

Ich verstehe total nicht wovon du sprichst! :) ABER - 1) ich kann void * nur in der exportierten C-Funktion haben, weil ES C-exportierte Funktion ist! ;) 2) es ist das erste Mal, dass ich höre "C++ Compiler kann die korrekte Zeigerarithmetik abhängig vom Layout der Objekte ausführen" .. was ??:) –

+0

BTW - was ist falsch an meinem Design? Nun - ich liebe C++, aber gemeinsame Bibliotheken erlauben nur C exportierte Funktionen. Was sollte ich dann tun? Werde lahmer C-Coder und gebe alles gute auf, was C++ ofers? :) –

+0

Das Problem ist, dass Sie davon ausgehen, dass ein Objekt in C++ eine eindeutige Adresse hat, egal wie Sie darauf verweisen. Dies ist leider nicht der Fall. Ich bearbeite meinen Post, um ein kleines Beispiel mit Realworld-Werten hinzuzufügen, um es zu veranschaulichen. –

Verwandte Themen