Symbolsichtbarkeit und Namespace

Lesezeit: 7 Minuten

Ich experimentiere mit der Sichtbarkeit von C++-Symbolen unter Linux und gcc. Es scheint, dass die bevorzugte Methode darin besteht, -fvisibility=hidden zu verwenden und die verwendeten Symbole nacheinander gemäß der Visibility gcc-Wiki-Seite (http://gcc.gnu.org/wiki/Visibility). Mein Problem ist das viele Bibliotheken handhaben dies nicht gut, sie vergessen, Symbole explizit zu exportieren, was ein ernstes Problem darstellt. Nach mehreren behobenen Fehlern können sogar einige Teile des Boosts noch betroffen sein. Natürlich sollten diese Fehler behoben werden, aber bis dahin möchte ich einen “sicheren” Weg verwenden, um so viele Symbole wie möglich auszublenden.

Ich habe eine Lösung gefunden: Ich platziere alle Symbole in einem Namensraum und verwende das Symbol zum Ausblenden des Attributs und exportiere die öffentliche Schnittstelle, auf diese Weise können nur meine Symbole beeinflusst werden.

Das Problem ist, dass ich eine Warnmeldung bekomme, wenn ich für jede Klasse, die ich nicht exportiert habe, etwas gegen diese Bibliothek kompiliere und ich in der Anwendung als Klassenfeld verwende.

namespace MyDSO __attribute__ ((visibility ("hidden"))) {
  struct Foo {
    void bar() __attribute__ ((visibility ("default"))) {}
  };
}

struct Bar {
  MyDSO::Foo foo;
};

int main() {}

Die Warnmeldung kann in diesem kleinen Beispiel reproduziert werden, aber natürlich sollte sich der Namespace in einer Bibliothek der anderen Klasse in der Anwendung befinden.

$ gcc-4.7.1 namespace.cpp -o namespace
namespace.cpp:7:8: warning: ‘Bar’ declared with greater visibility than the type of its field ‘Bar::foo’ [-Wattributes]

Soweit ich die Sichtbarkeit von Symbolen verstehe, sollte das Ausblenden von Namespace einen ähnlichen Effekt haben wie die Verwendung von -fvisibility=hidden, aber ich habe bei letzterer nie ähnliche Warnungen erhalten. Ich sehe, dass die Klasse in der Anwendung auch ausgeblendet wird, wenn ich -fvisibility=hidden an die Anwendung übergebe, sodass ich keine Warnung erhalte. Aber wenn ich die Option nicht übergebe, erscheint dem Compiler keines der Symbole in den Headern verborgen, sodass ich keine Warnung mehr erhalte.

Was ist der Vorschlag dieser Warnmeldung? Ist es ein ernstes Problem? In welchen Situationen kann dies zu Problemen führen? Wie unterscheidet sich das Ausblenden von Namespace von fvisibility=hidden?

Symbolsichtbarkeit und Namespace
Niall Douglas

Bevor ich Ihre spezielle Frage beantworte, sollte ich für andere erwähnen, dass das Anwenden von Symbolsichtbarkeitsattributen pro Namespace eine GCC-spezifische Funktion ist. MSVC unterstützt nur dllexport für Klassen, Funktionen und Variablen, und wenn Sie möchten, dass Ihr Code portabel ist, müssen Sie dort mit MSVC übereinstimmen. Wie mein ursprünglicher Leitfaden zur GCC-Symbolsichtbarkeit (den Sie auf der GCC-Website verlinkt haben) darauf hinweist, kann die makrobasierte DLLexport-Maschinerie von MSVC leicht wiederverwendet werden, um etwas Ähnliches auf GCC zu erreichen “.

In Bezug auf Ihr spezifisches Problem ist GCC richtig, um Sie zu warnen. Wenn ein externer Benutzer versucht hat, den öffentlichen Typ Bar zu verwenden, muss er mit ziemlicher Sicherheit alles in Bar verwenden, einschließlich Bar::foo. Aus genau dem gleichen Grund müssen alle privaten Memberfunktionen sichtbar sein, obwohl sie privat sind. Viele Leute sind darüber überrascht, weil sie argumentieren, dass private Member-Funktionssymbole per Definition für niemanden zugänglich sind, aber sie vergessen, dass nur weil der Programmierer keinen Zugriff hat, dies nicht bedeutet, dass die Compiler nicht müssen Zugriff. Mit anderen Worten, private Memberfunktionen sind für Sie privat, aber nicht für den Compiler. Wenn sie in einer Header-Datei erscheinen, bedeutet dies im Allgemeinen, dass der Compiler Zugriff benötigt, selbst in einem anonymen Namespace (der nur für Programmierer anonym ist, nicht für Compiler, die dazu neigen, einen Hash des Inhalts als “echten” Namespace-Namen zu verwenden).

Das Ausblenden eines Namespace hat ganz andere Auswirkungen als -fvisibility=hidden. Dies liegt daran, dass GCC viele Symbole über die für einen bestimmten Typ hinaus ausspeist, z. B. für vtables, für type_info usw in denselben Prozess mit kollidierenden Symbolen, z. B. zwei gemeinsame Objekte, die mit verschiedenen Versionen von Boost erstellt wurden.

Ich schätze Ihre Versuche, die Probleme zu beheben, die durch die Sichtbarkeit von Symbolen in ELF verursacht werden, und die Folgen für defekte C++-Binärdateien und viel verlorene Programmierproduktivität. Sie können sie jedoch nicht beheben – sie sind Fehler in ELF selbst, das für C und nicht für C++ entwickelt wurde. Wenn es Sie tröstet, ich habe vor einigen Monaten ein internes BlackBerry-Whitepaper zu diesem Thema geschrieben, da Probleme mit der Sichtbarkeit von ELF-Symbolen für uns in BB10 genauso ein Problem darstellen wie für jedes große Unternehmen mit einer bedeutenden C++-Codebasis. Vielleicht sehen Sie also einige Lösungsvorschläge für C++17, insbesondere wenn die Implementierung der C++-Module von Doug Gregor gute Fortschritte macht.

  • Danke für deine ausführliche Antwort. Nur aus Neugierde interessiert mich, welche Symbole der Compiler benötigt, die nicht generiert werden können? Soweit mir bekannt ist, können in trivialen Klassen sogar Typinfos generiert werden. Wenn Sie vis=hidden verwenden, erhalten Sie keine Warnung, selbst wenn Sie Symbole ausblenden, die nicht ausgeblendet werden sollen, sondern nur ein undefinierter Symbolfehler vom Linker. Die Verwendung des versteckten Namespace gcc kann das Problem erkennen. Vielleicht gibt es legitimen Nutzen, nur einige Symbole in einer Klasse zu exportieren, aber gcc gibt trotzdem eine Warnung aus. Doug Gregors C++ Modules ist sehr interessant, ich mochte seine Präsentation, danke fürs Teilen.

    – VargaD

    25. März ’13 um 17:50


  • Kurz gesagt, typeinfo für jede Klasse mit virtual wird immer ausgegeben, während typeinfo für Klassen ohne nur ausgegeben wird, wenn typeid() oder etwas, das es verwendet (Exception catches, dynamic_cast<> usw.) verwendet wird. Außerdem geben die meisten Compiler zwei oder mehr Konstruktorimplementierungen pro programmspezifiziertem Konstruktor aus, und auch die Destruktorgenerierung hat etwas Magie. Kurz gesagt, -fvisibility=hidden verbirgt vieles, und Ihre Methode würde nur die vom Programmierer angegebenen Dinge verbergen und nicht die magischen Interna. Vieles davon wird mit C++-Modulen einfacher, wenn auch überhaupt nicht gelöst. Niall

    – Niall Douglas

    31. März ’13 um 21:43

Ihre Verwendung der Sichtbarkeitsattribute erscheint mir rückständig; Ich denke, Sie hätten bessere Ergebnisse mit -fvisibility=hidden und dem Hinzufügen von Sichtbarkeit “default” zum Namespace der Bibliotheksdeklaration, da die Schnittstelle der Bibliothek vermutlich eine Standardsichtbarkeit hat oder Sie sie nicht von Ihrer Anwendung aus verwenden können. Wenn Sie die Bibliotheksheader nicht ändern möchten, können Sie #pragma GCC-Sichtbarkeits-Push/Pop um Ihre #includes verwenden.

Wie Niall sagt, funktioniert das Markieren einzelner Memberfunktionen als Standard nicht, der gesamte Foo-Typ muss eine Standardsichtbarkeit haben, wenn er Teil der Benutzeroberfläche der Bibliothek ist.

  • Danke für deine Antwort. ich sehe nicht warum die ganze Klasse muss exportiert werden. Die meisten Klassen haben nicht einmal eine Vtable. Ich habe viele Testanwendungen und sogar versucht, nicht exportierte Ausnahmen auszulösen, aber alle haben ziemlich gut funktioniert. Ich versuche zu verstehen, warum es exportiert werden muss. Wissen Sie, unter welchen Umständen es fehlschlägt? Können Sie ein Beispiel zeigen?

    – VargaD

    19. April 13 um 20:36 Uhr

  • Das grundlegende Problem besteht darin, dass Klassen in C++ über eine Verknüpfung verfügen; Wenn eine Klasse versteckte Sichtbarkeit hat, bedeutet dies, dass Sie nicht planen, sie außerhalb ihres eigenen DSO zu verwenden. Es macht daher keinen Sinn, einer Memberfunktion eine größere Sichtbarkeit als ihrer Klasse zu geben, da Sie die Klasse verwenden müssen, um die Memberfunktion zu verwenden. Warum möchten Sie nicht die Klasse selbst exportieren?

    – Jason Merrill

    23. Mai ’13 um 12:48


  • Mein Problem ist, dass das Exportieren der gesamten Klasse nicht gewartet werden kann, Symbole werden leicht exportiert. In C exportieren Sie mit -fvisibility-hidden nur Symbole, die Sie exportieren möchten, aber in C++ müssen Sie die gesamte Klasse exportieren und es ist schwierig, alle Symbole auszublenden, die Sie nicht exportieren möchten. Ich sehe, dass Klassen, die vtable haben, exportiert werden müssen. Ich habe oft gelesen, dass in C++ die Sichtbarkeit von Symbolen viele Probleme verursachen kann, und ich sollte Klassen mit exportierten Methoden nicht ausblenden, aber ich kann nicht sehen, warum. Ich habe versucht, wegen der Sichtbarkeit Laufzeitfehler zu erzeugen, aber es gelang mir nicht.

    – VargaD

    29. Mai ’13 um 19:33

  • @VargaD vielleicht spät, aber ich denke, Sie sollten die Pimpl-Methode ausprobieren, sie ist im Buch API Design for C++ von Martin Reddy gut beschrieben.

    Benutzer1598585

    7. September ’14 um 23:41

.

263500cookie-checkSymbolsichtbarkeit und Namespace

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy