Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich habe gerade festgestellt, dass g++ inline offenbar ignoriert. Kann es sein, dass ich da irgendetwas falsch gemacht oder vergessen habe? Hier mal das Stückchen Quellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | ...
if( stat( *nam, &fstat ) == 0 ) {
update_flag = false;
if( tnp->get_mtime() != fstat.st_mtime ) {
update_flag = true;
// this forces an update
tnp->set_flags( tnp->get_flags() & ~THMB_MARKED ); // <<<<<<<<<<<<<
} else if( tnp->get_size() != fstat.st_size ) {
update_flag = true;
tnp->set_flags( tnp->get_flags() & ~THMB_MARKED );
} else if( (use_thumbs != ((tnp->get_flags() & THMB_EXTERN) == THMB_EXTERN ? 1 : 0)) ){
// update wanted because of changed settings by user
update_flag = true;
}
...
|
Nemiver zeigt mir dazu folgenden Assemblercode:
update_flag = true;
0x000000000040ad8e <MainWindow::update_dir()+414>: movb $0x1,-0xd5(%rbp)
// this forces an update
tnp->set_flags( tnp->get_flags() & ~THMB_MARKED );
0x000000000040ad95 <MainWindow::update_dir()+421>: mov -0xb8(%rbp),%rax
0x000000000040ad9c <MainWindow::update_dir()+428>: mov %rax,%rdi
0x000000000040ad9f <MainWindow::update_dir()+431>: callq 0x40cdec <ThumbBox::get_flags()>
0x000000000040ada4 <MainWindow::update_dir()+436>: and $0xfffffffd,%eax
0x000000000040ada7 <MainWindow::update_dir()+439>: movswl %ax,%edx
0x000000000040adaa <MainWindow::update_dir()+442>: mov -0xb8(%rbp),%rax
0x000000000040adb1 <MainWindow::update_dir()+449>: mov %edx,%esi
0x000000000040adb3 <MainWindow::update_dir()+451>: mov %rax,%rdi
0x000000000040adb6 <MainWindow::update_dir()+454>: callq 0x40cdcc <ThumbBox::set_flags(short)>
0x000000000040adbb <MainWindow::update_dir()+459>: jmpq 0x40ae50 <MainWindow::update_dir()+608>
} else if( tnp->get_size() != fstat.st_size ) {
0x000000000040adc0 <MainWindow::update_dir()+464>: mov -0xb8(%rbp),%rax
0x000000000040adc7 <MainWindow::update_dir()+471>: mov %rax,%rdi
0x000000000040adca <MainWindow::update_dir()+474>: callq 0x40ce7e <ThumbBox::get_size()>
0x000000000040adcf <MainWindow::update_dir()+479>: movslq %eax,%rdx
0x000000000040add2 <MainWindow::update_dir()+482>: mov -0x80(%rbp),%rax Die beiden Methoden, um die es geht sind:
| inline void set_flags( short flg ) { flags = flg; }
inline short get_flags( void ) { return flags; }
|
Die beiden callq Befehle sind doch eindeutig Unterprogrammaufrufe.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Hallo! Wenn du g++ mit Optimierungen aufrufst, kann es sein, dass er inline einfach wegoptimiert. Ruf den mal mit g++ -O0 auf und prüfe dagegen.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Das ändert nichts, hatte ich auch nicht wirklich erwartet. Ich habe jetzt aber mal die Optimierung eingeschaltet (-O1 und -O2). Das ergibt Code den Nimever nicht mehr vernünftig anzeigen kann und wo ich auch nicht wirklich einen Zusammenhang mit meinem Quellcode entdecken kann. Also wenn nimever in der markierten Zeile auf einen breakpoint läuft und ich dann die Assembleransicht einschalte, bekomme ich folgendes:
int add_flg = 0;
0x0000000000409fad <MainWindow::update_dir()+599>: movl $0x0,0x4(%rsp)
bool update_flag;
char **namelist, **nam;
ThumbBox * tnp;
struct stat fstat;
Die nachfolgenden Zeilen stammen aus dem Kopf der entsprechenden Funktion etwa 30 Zeilen vorher. Das sieht völlig unbrauchbar aus. Ich würde mir ja gerne den gesamten Quelltext ansehen, aber erstens weiß ich nicht wie ich das in ein Makefile mit fast 30 Modulen einbauen soll und zweitens würde da wohl meine Platte überlaufen. Aber das Programm funktioniert noch.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich habe mich jetzt mal hier durchgekämpft. Manche Absätze musste ich mehrmals lesen und ich bin immer noch nicht sicher ob ich die verstanden habe.
Using -Winline warns when a function marked inline could not be substituted, and gives the reason for the failure.
Habe ich gemacht. Eine Warnung habe ich bei keinem Versuch gesehen.
GCC does not inline any functions when not optimizing unless you specify the ‘always_inline’ attribute for the function, ...
Ok, habe das auf -O1 geändert. Ergebnis kann ich, wie oben erwähnt, nicht überprüfen.
Therefore, a non-static inline function is always compiled on its own in the usual fashion.
Dann sind inline Funktionen komplett überflüssig! Statische Funktionen haben keinen Bezug zur jeweiligen Instanz einer Klasse. Damit kann ich dann keine Variablen abfragen oder ändern. Da bin ich besser dran, wenn ich die entsprechenden Variablen öffentlich mache und dann direkt darauf zugreife. Das kann aber nicht im Sinne des Erfinders sein.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
inline void foo (const char) __attribute__((always_inline));
Wieder was gelernt. Ich dachte bisher immer, die GCC optimieren das weg. Ich kenne das aus C eigentlich™ so, dass inline nur greift, wenn es wirklich ausschliesslich im Header deklariert UND definiert wird. In C++ ohne static ist inline sinnfrei, weil das Object ja sowieso schon erstellt werden muss, da kann man auch die Funktion dort ablegen, vor allem wenn Zugriff auf Objektvariablen bestehen. Inline dürfte ja nur Vorteile bringen, wenn man dort statische Funktionen ablegen kann. Aber das genaue geht dann etwas zu tief in die Materie. Da warte mal auf die Berufsprogrammierer 😉
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Da warte mal auf die Berufsprogrammierer
Da wird mir wohl nichts anderes übrig bleiben.
Ich kenne das aus C eigentlich™ so, dass inline nur greift, wenn es wirklich ausschliesslich im Header deklariert UND definiert wird.
Das mache ich ja. Hier mal ein anderes Beispiel, da ich den Code des aktuellen Falls nicht zurückdrehen will und mein Backup Server gerade nicht läuft. Dieser Code:
| // ComboBox.cpp
void ComboBox::maxwidth( int w ) {
max_width = w;
}
|
soll z.B. durch:
| // ComboBox.h
class ComboBox : public Fl_Group {
private:
...
int max_width;
...
public:
inline void maxwidth( int w ) { max_width = w; }
...
};
|
ersetzt werden. Beim Schlüsselwort "static" muss man übrigens aufpassen. Bei C bedeutet das, dass die Funktion nur innerhalb der Programmdatei sichtbar ist. Bei C++ ist das etwas anders. Da ist die Funktion immer da und ist jederzeit über den Bereichsoperator aufrufbar. Und das auch ohne das zuvor mit new ein entsprechendes Objekt erstellt wurde. Bei der verlinkten Dokumentation ist mir auch nicht immer klar, ob die sich nur auf C++ oder auch auf C bezieht. P.s. Ich habe mein FLTK Toolkit aus dem Quelltext installiert. Da sind die inline Funktionen auch so realisiert und scheinen auch zu funktionieren. Jedenfalls lande ich beim Debuggen oft in deren Header Dateien, wo sich der Inlinecode befindet. Die scheinen -Os verwendet zu haben, was bei mir aber keine Änderung bewirkt.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Jein. Du kannst in C ja auch innerhalb eines struct ein static int definieren, z.B. als struct-Counter. Zumindest ab c99, bei älteren weiß ich das nicht. Bei C++ kannst du zwar Methoden static deklarieren, die dürfen sich aber nicht auf Elemente beziehen, die der Klasse angehören, wie deine width. Bei C++ kenn ich das eher so:
1
2
3
4
5
6
7
8
9
10
11
12 | class whatever {
public:
whatever();
~whatever();
static int foo( int i ) { return i+i*i-1; } // gültig
static int width( int i ) { _zahl = i; } // ungültig, da es kein Objekt zum zuweisen gibt, also auch kein _zahl
private:
int _zahl;
};
# Aufruf mit
int i = whatever::foo( 33 );
|
Dein MaxWidth hat ja kein Object, also auch keine privaten Variablen. Du kannst dir die static Funktionen als „Teilfunktionen“ vorstellen. Bspw. um Strings zu bearbeiten oder Sortierfunktionen, etc. bei denen Ein- und Ausgabe ein geschlossener Vorgang sind. Um ein max_width zu setzen, brauchst du zunächst eine Instanz von der ComboBox, die den Speicherbereich reseviert hat — auch den für Instanz.max_width.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ja, ich kann natürlich sowohl in C als auch in C++ innehalb einer Funktion eine statische Variable deklarieren. Das ist dann so etwas wie global, immer vorhanden, aber nur lokal sichtbar. Hatte ich früher öfters gemacht um z.B. Strings zurückzugeben. Das ist aber nicht thread-save, weshalb ich mir das abgewöhnt habe.
Bei C++ kannst du zwar Methoden static deklarieren, die dürfen sich aber nicht auf Elemente beziehen, ...
Dürfen ist falsch, sie können nicht! Statische Methoden werden in aber gerne für Callback Funktionen verwendet, weil deren Adresse schon zur Compilezeit bekannt ist. Für den Zugriff auf Elemente der jeweiligen Klasse muss ihnen aber ein Entsprechender Zeiger übergeben werden.
Dein MaxWidth hat ja kein Object,...
Stimmt, aber ein Element eines Objekts. Wenn ich ein entsprechendes Objekt mit new erzeugt habe, sollte doch:
mein_object->maxwidth( 200 );
auch als Inline-Funktion funktionieren. Die Alternative wäre max_width öffentlich zu machen und dann so anzusprechen:
mein_object->max_width = 200;
Das geht in jedem Fall, ist aber nicht im Sinne von OOP.
Um ein max_width zu setzen, brauchst du zunächst eine Instanz von der ComboBox, die den Speicherbereich reseviert hat — auch den für Instanz.max_width.
Die Bedingung ist erfüllt, dadurch, dass zuvor
| nein_object = new ComboBox(...);
|
gelaufen ist. Um das "max_width" mal zu demonstrieren zeige ich mal ein Bild davon. Man sieht hier, dass die aufgeklappte Liste etwas breiter ist, als das eigentliche Eingabefeld. Mit max_width wird die maximale Erweiterung begrenzt.
- Bilder
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Ich glaube ich habe nur nicht verstanden, worauf du hinaus willst. Du möchtest das inline verwenden, um einen einmaligen Funktionspointer zu generieren, der für jede Instanz der Klasse gilt, aber separat im Speicher liegt? Das ist aber wirklich mehr Compiler-Arbeit. Ehrlich gesagt dachte ich, dass Klassen genau so „ab Werk“ funktionieren. Es wird im Speicher einmal die Methode hinterlegt und jede Instanz der Klasse nutzt diese Methode, nur mit den eigenen Variablen, was auch die callq-Aufrufe erklärt, die im Speicher ja auch nebeneinander liegen.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Du möchtest das inline verwenden, um einen einmaligen Funktionspointer zu generieren, der für jede Instanz der Klasse gilt, aber separat im Speicher liegt?
Nein, ich möchte einfach nur den direkten Zugriff auf Variablen der jeweiligen Klasse vermeiden indem ich sog. Getter oder Setter verwende die der Compiler dann ohne den Overhead eines echten Funktionsaufrufs realisiere soll(te). Bei einzeiligen Funktionen sollte das doch problemlos möglich sein. In Schleifen ist es störend, wenn der Compiler sich da quer stellt. Die Optimierung habe ich normalerweise nicht eingeschaltet, da das beim debuggen immer wieder mal zu Verwirrungen führt. Aber das Problem scheinen andere auch zu haben, wie man hier sehen kann.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12829
|
ChickenLipsRfun2eat schrieb: Ich glaube ich habe nur nicht verstanden, worauf du hinaus willst. Du möchtest das inline verwenden, um einen einmaligen Funktionspointer zu generieren, der für jede Instanz der Klasse gilt, aber separat im Speicher liegt?
Was Du da schreibst, ist widersprüchlich: Wenn eine Funktion geinlined wird, dann gibt es keinen Funktionszeiger. Das ist ja gerade der Witz. Ich habe gerade keine Zeit, Eure Beiträge alle zu lesen, ich gebe aber zu bedenken, dass inline nur in Hinweis an den Compiler ist. Selbst eine Compilerimplementierung, die inline stumpf ignoriert, ist standardkonform.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Selbst eine Compilerimplementierung, die inline stumpf ignoriert, ist standardkonform.
Das ist bekannt. Aber wenn ich das von Anfang an weiß, kann ich mich darauf einstellen und bei zeitkritischen Dingen auf öffentliche Variablen zurückgreifen. Aber ich habe jetzt einen Weg gefunden, g++ zu überreden. Hat etwas gedauert, weil die Doku da als Beispiel nur Prototypen enthält. Meine Header Datei sieht jetzt so aus:
| inline void set_flags( short flg ) __attribute__((always_inline)) { flags = flg; }
inline short get_flags( void ) __attribute__((always_inline)) { return flags; }
|
Anfangs hatte ich versucht das Attribut ans Zeilenende zu hängen. Das hataber nur bei einer Funktion funktioniert. Der generierte Code ist jetzt:
tnp->set_flags( tnp->get_flags() & ~THMB_MARKED );
0x000000000040adb0 <MainWindow::update_dir()+448>: and $0xfffffffd,%eax
0x000000000040adb3 <MainWindow::update_dir()+451>: cwtl
0x000000000040adb4 <MainWindow::update_dir()+452>: mov -0xd0(%rbp),%rdx
0x000000000040adbb <MainWindow::update_dir()+459>: mov %rdx,-0xc8(%rbp)
0x000000000040adc2 <MainWindow::update_dir()+466>: mov %ax,-0xf0(%rbp)
} else if( tnp->get_size() != fstat.st_size ) {
Das ist doch schon deutlich kürzer. Ich überlege jetzt noch dafür eine Makro Definition zu verwenden. Die könnte man dann an andere Compiler anpassen oder ggf. die Auflösung leer lassen.
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4578
Wohnort: Berlin
|
Das ist alles ein bisschen sehr viel Voodoo und unnötig GCC-spezifisch. Ich würde einfach Optimierungen aktivieren und entweder hoffen das der Compiler guten Code erzeugt oder nachgucken ob der guten Code erzeugt. Wenn das verwendete Werkzeug dafür dann nicht mit optimiertem Code klar kommt, dann ist das halt ungeeignet für diesen Zweck. Speziell das letzte Beispiel ist vollkommen Banane, denn wenn man einen trivialen Getter und Setter für ein Member hat, dann ist das faktisch ein Öffentliches. Das ist nur dann nicht im Sinne von OOP wenn geplant ist das abgeleitete Klassen das mit eigenem Verhalten überschreiben können. Was dann aber natürlich auch nicht ge-inlined werden kann, den dann braucht man ja zwingend eine Indirektion über einen austauschbaren Zeiger. Die inline -Schlüsselworte vor den beiden Methoden sind überflüssig, denn das ist bei C++ bereits implizit der Fall wenn eine Methode in der Klassendefinition definiert wurde und nicht nur deklariert. Zwei Punkte: 1. Man kann einen C++-Compiler qua Standard nicht zwingen etwas zu inlinen und 2. Mit aktivierten Optimierungen werden teilweise sogar nicht als inline gekennzeichnete Aufrufe ge-inlined und eine Tonne von anderen Optimierungen durchgeführt, die so ein umständliches „ich will aber unbedingt das diese Funktion/Methode inline ist, aber sonst bitte nix am Code optimieren“ bei weitem übertrifft. Das ist sinnfreies unportables rumgefrickel an der falschen Stelle, IMHO.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ja, ist unschön. Ich würde einfach Optimierungen aktivieren und entweder hoffen das der Compiler guten Code erzeugt oder nachgucken ob der guten Code erzeugt. Wenn das verwendete Werkzeug dafür dann nicht mit optimiertem Code klar kommt, dann ist das halt ungeeignet für diesen Zweck.
Das nachsehen, ob guter Code erzeugt wurde ist ja daran gescheitert, das Nemiver den Assembler Code nicht mehr zusammenhängend darstellen kann. Ganz allgemein scheint das debuggen von optimierten Code problematisch zu sein, da Variablen oft bereits weg optimiert sind, wenn der Fehler erkannt wurde. Auf welche Werkzeuge kann ich denn ausweichen? ddd ist inzwischen (fast) komplett unbrauchbar und Nemiver kann nicht alles und krankt bei Mate an der fehlerhaften Anpassung an GTK (Fenstergrößen und Scrollbalken falsch). Zuletzt musste ich zur Auswertung eines Core Dumps gdb auf der Konsole bemühen, was ich sehr mühsam fand.
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4578
Wohnort: Berlin
|
Fehlersuche in optimiertem Code ist naturgemäss problematisch, deswegen hat man in der Regel in IDEs ja auch die Unterscheidung zwischen einem „debug build“ wo keine Optimierungen gemacht werden und man einen Haufen assert s live schalten kann, und einem „release build“. So einen Einzelschritt-Debugger brauche ich aber auch zunehmend seltener, was ich unter anderem auf Unit-Tests zurückführen würde. Und das ich weniger in Sprachen programmiere, wo man sich um Speicherverwaltung, unbemerktes verlassen von Arraygrenzen, und hängende Zeiger Gedanken machen muss. Also Hochsprachen. Die Sprachen bieten auch mehr Abstraktion und Sicherheit als ”früher”. In C++ hantiert man ja beispielsweise nicht mehr so viel mit Indexzugriffen oder rohen Zeigern bzw. Zeigerarithmentik wie in C oder unmodernem C++. In modernem C++ hat man verschiedene ”Smartpointer” zur Verfügung, die einen auch vor Fehlern bewahren können, oder sie zumindest leichter auffindbar machen. Nicht nur die Tests selbst helfen beim Fehler finden und beseitigen, sondern auch, dass man sich angewöhnt schon beim Schreiben leichter testbaren Code zu produzieren, inklusive assert s für Randbedingungen und Invarianten.
|