Undefined Reference bei static const Member in std::max
-
Hallo,
zu diesem harmlosen Stück Code:
#include <iostream> #include <algorithm> struct foo { static int const bar = 23; }; int main() { std::cout << foo::bar << std::endl; // geht... std::cout << std::max((int)foo::bar, 42) << std::endl; // geht auch... std::cout << std::max(foo::bar, 42) << std::endl; // geht nicht! }
...sagt mein Compiler:
/tmp/ccV2oQrn.o: In function `main': test.cc:(.text+0xe4): undefined reference to `foo::bar' collect2: ld returned 1 exit status
Der Fehler tritt aber nur mit der letzten Zeile auf. Wenn ich die auskommentiere, ist des Compilers Welt wieder in Ordnung.
Ähh... wie bitte?!
-
Hmm. Visual Studio kompiliert das ohne Probleme.
Was hast du für einen Compiler?
-
Der GCC 3 hat damit bei mir so die selben seine Probleme:
`
[Linker error] undefined reference to `foo::bar'
ld returned 1 exit status
`
Visual C++ 9 kann das bei mir ebenfalls tadellos.
-
Hm, getestet hatte ich mit g++ 4.3.3 und 4.1.2, bei beiden war das Problem das selbe. Wenn das wirklich ein Bug in g++ ist, dann scheint der sich schon durch viele Compilerversionen gezogen zu haben...
-
Den Fehler kann ich mit dem gcc 3 bestätigen. Verstehe aber auch nicht, woran es liegen könnte.
-
SeppJ schrieb:
Den Fehler kann ich mit dem gcc 3 bestätigen. Verstehe aber auch nicht, woran es liegen könnte.
Am fehlerhaften Code. Eine statische Memberkonstante, die in der Klassendefinition initialisiert wird, muss dennoch zusätzlich definiert werden, wenn sie in einem Kontext verwendet wird, in dem kein konstanter Ausdruck erforderlich ist. Das Binden an einen Funktionsparameter erfordert keinen konstanten Ausdruck, folglich ist hier die Definition in allen Fällen erforderlich. Wir haben es hier mit einer ODR-Verletzung zu tun - keine Diagnose erforderlich.
-
Anders formuliert: Das, was Du geschrieben hast, enthält keine "richtige Definition" der Variable (es wird kein Speicher dafür angelegt). Dennoch erlaubt Dir ein standardkonformer Compiler die eingeschränkte Nutzung als konstanten Ausdruck ohne Definition. Das schließt aber die Bindung an Referenzen oder die Anwendung des Adressoperators aus. Dafür bräuchtest Du dann eine "richtige Definition". Da std::max die Parameter über Referenzen entgegen nimmt, meckert der Linker...
Gruß,
SP
-
std::cout << std::max((int)foo::bar, 42) << std::endl; // geht auch...
Hmm. Visual Studio kompiliert das ohne Probleme.
Dann hätte beides nicht funktionieren dürfen, sehr verwirrend. Ok Visual Studio hat vielleicht da schon den C++0x Standard, aber das mit dem (int) beim GCC? Wenn ich das richtig sehe legt er hier automatisch eine lokale Variable an, sollte er das?
Ich meine das er das so umformen könnte wie in:
std::cout << std::max( (int)5, 42 ) << std::endl;
aber sollte das auch?
Und der richtige Code nur nochmal zum Nachvollziehen:
#include <iostream> #include <algorithm> struct foo { static int const bar = 23; }; int const foo::bar; // out-of-class definition int main() { std::cout << foo::bar << std::endl; // geht... std::cout << std::max((int)foo::bar, 42) << std::endl; // geht auch... std::cout << std::max(foo::bar, 42) << std::endl; // jetzt gehts! }
-
Ist denn noch etwas unklar? Ich verstehe Deinen Post nicht ganz. Es ist jedenfalls kein "Fehler im GCC". Keine Ahnung, was der Microsoft-Compiler da macht.
-
DeepCopy schrieb:
std::cout << std::max((int)foo::bar, 42) << std::endl; // geht auch...
Hmm. Visual Studio kompiliert das ohne Probleme.
Dann hätte beides nicht funktionieren dürfen, sehr verwirrend. Ok Visual Studio hat vielleicht da schon den C++0x Standard, aber das mit dem (int) beim GCC?
Das hat mit zukünftigen Standards nichts zu tun, zudem ändert sich in dieser Hinsicht ohnehin nichts.
Der Unterschied zwischen den verschiedene Codes besteht darin, dass instd::cout << foo::bar << std::endl; std::cout << std::max((int)foo::bar, 42) << std::endl;
der Wert von foo::bar vor Aufruf der jeweiligen Funktion ermittelt wird.
Der << Operator bekommt sein rechtes Argument per value, also wird der Wert von bar hier kopiert. Es ist so trivial, dass hier der Wert zur Erzeugung des Funktionsparameters unmittelbar im Code eingesetzt werden kann, dass das offenbar schon bei niedrigster Optimierungsstufe passiert. Im Fall std::max((int)foo::bar, 42) ist dies ebenso, das Resultat des Casts ist ein rvalue, es wird folglich ein lvalue-zu-rvalue-Konvertierung induziert (deren Ergebnis unmittelbar feststeht), das Binden an den Funktionsparameter (ref-auf-
const) erzeugt ein temporäres Objekt für diesen Parameter. Im Ergebnis existiert im Objektcode keine nichtaufgelöste Referenz auf foo::bar und das Linken kann problemlos durchgeführt werden.std::cout << std::max(foo::bar, 42) << std::endl;
ist anders, denn hier wird foo::bar unmittelbar an den Funktionsparameter von max gebunden, der eigentliche Zugriff findet erst in max statt, und wenn z.B. die Funktion nicht inlinesubstituiert wird, führt dies notwendig zu einer unaufgelösten Referenz im Objektcode, schließlich zu einem Linkerfehler. Auch g++ hat mit dem ursprünglichen Code keine Probleme, wenn die Optimierungsstufe wenigstens O1 ist, während Visual C+++ den Fehler ebenfalls bringt, wenn jede Optimierung ausgeschaltet wird.
-
Sebastian Pizer schrieb:
Ist denn noch etwas unklar? Ich verstehe Deinen Post nicht ganz. Es ist jedenfalls kein "Fehler im GCC". Keine Ahnung, was der Microsoft-Compiler da macht.
So..?
Und warum akzeptiert der Compiler eine Konstante mal und mal nicht?
Wenn:std::cout << std::max(foo::bar, 42) << endl; // für die konstante 10 steht und std::cout << std::max( 10 , 42) << endl; // für die konstante 10 steht warum std::cout << std::max((int) 10, 42) << endl; // dann dieser Umstand ?
Er sollte das doch genau komplilieren wie Visual Studio, oder?
-
DeepCopy schrieb:
Und warum akzeptiert der Compiler eine Konstante mal und mal nicht?
Wenn:std::cout << std::max(foo::bar, 42) << endl; // für die konstante 10 steht und std::cout << std::max( 10 , 42) << endl; // für die konstante 10 steht warum std::cout << std::max((int) 10, 42) << endl; // dann dieser Umstand ?
Er sollte das doch genau komplilieren wie Visual Studio, oder?
foo::bar
ist ein lvalue,10
nicht,foo::bar+0
auch nicht. Bei den Rvalue-Ausdrücken wird temporär ein int angelegt, damit eine Referenz gebunden werden kann.foo::bar
wird im ersten Fall direkt an den Referenz-Parameter vonstd::max
gebunden. Dann darf es auch nicht wundern, dass der Linker meckert. Der Compiler will derstd::max
-Funktion ja die Adresse desfoo::bar
Objektes übergeben, welches nirgends definiert wurde.edit: Kann man sich drüber streiten, ob diese Regelung so sinvoll ist. Man hätte es ja auch so festlegen können, dass die Zuweisung in der Klasse gleichzeitig eine Definition ist und die ODR so anpassen können, dass Mehrfachdefinitionen einfach "zusammengefasst" werden (wie bei inline-Funktionen und Templates auch) -- Aber das geht dann auf Kosten des Speichers. Bei der Template-Metaprogrammierung fallen ja eventuell viele solche Konstanten an, die dann alle ihren Speicher zugeordnet bekommen. Das ist natürlich auch Käse.
-
Sebastian Pizer schrieb:
Bei den Rvalue-Ausdrücken wird temporär ein int angelegt, damit eine Referenz gebunden werden kann
Das wäre mir neu...
void barfoo(int &i) { i = 2; }; int main() { barfoo(5); }
Und das sagt der Compiler dazu:
Fehler: ungültige Initialisierung einer nicht-konstanten Referenz des Typs »int&« von temporärem Wert des Typs »int«|
Allerdings funktioniert es bei ref auf const type so
[void barfoo(const int &i) { ; // todo irgendwas }; int main() { barfoo(5); }
-
std::max nimmt Referenzen auf const.
-
@camper: Danke... von dir kann man immer was lernen.
@Sebastian Pizer: Auch ein Danke... für deine Geduld
-
Danke Euch, man lernt halt nie aus...
Trotzdem irgendwie gut zu wissen, daß nicht nur ich am Anfang erstmal ratlos war.