no need of sun to light the way…

Wie mache ich meinen Linker wieder glücklich?

Manchmal sitzt man trotz jahrelanger C++-Erfahrung vor so manchem Konstrukt und denkt sich für ein Sekündchen „WTF?!“ – so heute mal wieder geschehen. Ein eigentlich trivialer Linkerfehler hat mich dazu gebracht, mal ein wenig über eine möglichst knappe und mit wenig Aufwand verbundene Lösung nachzudenken.

Aber mal Eins nach dem Anderen: Einer meiner Kollegen ist momentan im Urlaub, hat aber kurz vorher noch einen dicken Batzen Code auf den main-Branch geliefert. Und klar, natürlich würde niemand solche großen Änderungen liefern, wenn er sie nicht zumindest an seinem Rechner erfolgreich kompilieren konnte. Nur: Wenn bleistiftsweise der Microsoft-C++-Compiler ein Statement anstandslos frisst, heißt das noch lange nicht, dass etwa der gcc das täte. Und selbst wenn der Compiler mitspielt, gibt es ja immer noch den Linker, der die weiße Fahne hissen könnte – wie hier geschehen.

Nehmen wir mal folgendes absolut standardkonforme Konstrukt:

class Foo 
{  
public:  
    static const int CONSTANT = 1;  
};

Die Klasse „Foo“ hat also einen konstanten Member namens „CONSTANT“. Das an sich führt nun noch nicht zu einem Linkerfehler. Lustig wird das Ganze erst, wenn man diese Konstante nun wie eine gewöhnliche Variable an eine Funktion übergeben möchte, die gerne eine Referenz als Parameter hätte:

void bar(const int& ref) { … }
bar(Foo::CONSTANT);

Während der Microsoft-Linker hier ohne mit der Wimper zu zucken sein Binary zusammenschnürt, bekam ich – als Stellvertreter des besagten Urlaub habenden Kollegen – nun diverse Meckermails von unserem Continuous-Integration-System (Jenkins im Übrigen), dass der Build unter Linux fehlgeschlagen sei. Und zwar wegen „undefined reference to `Foo::CONSTANT’“. Hmpf. Wobei er ja nicht Unrecht hat, der Linker. Für eine Konstante reserviert der Compiler nun mal keinen Speicher, da kann ich mich noch so auf den Kopf stellen, Foo::CONSTANT kann so nicht als Referenz an bar() übergeben werden.

Klar, das Einfachste wäre jetzt gewesen, den Compiler explizit dazu zu bringen, Speicher anzulegen (was der Microsoft-Compiler wohl implizit macht). Da aber mein Kollege die betroffene (recht leichtgewichtige) Klasse komplett inline im Header angelegt hatte, hätte ich jetzt noch ein extra .cpp-File erzeugen sowie das Skript für CMake anpassen müssen. Zu viel Aufwand wegen so eines Bisschens. Man ist ja nicht umsonst Entwickler geworden, wäre man nicht so bequem veranlagt. 😉

Die zweite Lösung, die dann ohne ein zusätzliches .cpp-File auskäme, wäre ein Typecast. Dabei würde aber die Zeile (oben angeführter Code ist nur ein Beispiel) die Zeichenbegrenzung, die unsere firmeninternen Coding Guidelines für eine Zeile vorsieht, sprengen. Und ein Zeilenumbruch bei einem Funktions- bzw. Methodenaufruf mit nur einem Parameter? Das widerstrebte mir irgendwie.

Schlussendlich geht das Ganze aber auch noch simpler: Durch Hinzufügen eines einzigen Zeichens. Damit sieht der auf beiden Plattformen komplierende und linkende Funktionsaufruf dann so aus:

bar(+Foo::CONSTANT);

Ich musste ja nur den Compiler dazu bringen, dass er mir ein temporäres Objekt im Speicher erzeugt, auf das ich mir dann eine Referenz holen kann. Das passiert wie gesagt bei einem Typecast – aber ganz genauso erledigt dies auch der unäre +-Operator. Das Statement sieht dann zwar auf den ersten Blick wirklich ziemlich „WTF?!“ aus. Aber es macht wie gewünscht auch den Linker glücklich. 😀

Advertisements

3 Antworten

  1. NK

    Nächste Frage: Warum überhaupt erst ein const int als Referenz übergeben? Das ist ja schon irgendwie witzlos. 🙂

    25. Januar 2012 um 17:38

  2. Hm, ich hätte das Beispiel vielleicht doch etwas realitätsnäher gestalten sollen. Jedenfalls, unsere Coding Guidelines schreiben grundsäztlich vor, Parameter per Referenz zu übergeben, notfalls eben const, wenn man Änderungen vermeiden möchte. Hat einen einfachen Grund: Die Software muss halt auf mobilen Geräten mit vergleichsweise wenig Speicher auskommen. Unter 64 MB waren bis vor Kurzem keine Seltenheit.
    Mittlerweile kann man zwar immerhin von mindesten 64 MB RAM ausgehen – das ist aber auch nicht die Welt. Da macht zwar ein zusätzliches int, was jetzt mal temporär angelegt wird, wenn man eben nicht nur eine Referenz übergibt, den Kohl nicht unbedingt fett. Aber es könnte ja eben auch ein richtig dickes Objekt sein. 😉

    25. Januar 2012 um 18:44

  3. NK

    Okay, das stimmt natürlich. 🙂
    Nur beim int hatte es mich gewundert, da der Pointer, der dabei mutmaßlich in den Stack geschrieben wird, genau so viel Platz braucht wie ein int. Aber wenn’s den Guidelines dient 🙂

    25. Januar 2012 um 18:55

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s