Forum: Mikrocontroller und Digitale Elektronik c++ static class mit und ohne Konstructor


von pointer->wald (Gast)


Lesenswert?

Hi Leute,
hab eine Frage zu C++.

Ist der default Konstrucktor an, dann ist das Programm nur 1004 B groß.
Memory region         Used Size  Region Size  %age Used
        MFlash32:        1004 B        32 KB      3.06%
         RamLoc8:          40 B         8 KB      0.49%

Aktiviere ich meinen Konstrucktor wächst die Größe gleich auf 10952 B
Memory region         Used Size  Region Size  %age Used
        MFlash32:       10952 B        32 KB     33.42%
         RamLoc8:          80 B         8 KB      0.98%

Ich verstehe den Grund nicht...

Wo finde ich eine Erklärung dazu?
1
class test {
2
public:
3
    int get(){
4
       return x;
5
    }
6
    static test *inst(){
7
        static test x;
8
        return &x;
9
    }
10
    void set(int v){
11
        x = v;
12
    }
13
// mit Konstrucktor 10952 B code size
14
// mit default konstrucktor 1516 B
15
    test(){};
16
private:
17
        int x;
18
};
19
20
int main( void )
21
{
22
    test::inst()->set(7); 
23
return 0;
24
}
benutze den gnu c++ 14

von Oliver S. (oliverso)


Lesenswert?

Die Version mit Konstruktor erzeugt thread-safe-Code beim Zugriff auf x, 
die Version mit default Konstruktor nicht.

https://godbolt.org/z/TkNs5J

Warum? Gute Frage...

Oliver
P.S. Gilt für den PC, wenn du für eine ander Plattform kompiliert, wäre 
es natürlich nicht ganz unwichtig, für welche...

: Bearbeitet durch User
von Einer K. (Gast)



Lesenswert?

AVR-gcc 9.2 C++17 -Os
1
class Test
2
{
3
public:
4
  int get()
5
  {
6
    return x;
7
  }
8
  static Test &instance()
9
  {
10
    static Test x;
11
    return x;
12
  }
13
  void set(int x)
14
  {
15
    this->x = x;
16
  }
17
  // mit Konstruktor 180 Bytes code size
18
  // mit default konstrucktor 138 Bytes
19
  //Test() {};
20
private:
21
  int x;
22
};
23
24
int main( void )
25
{
26
  Test::instance().set(7);
27
  return 0;
28
}

Ohne Konstuktor wird das ganze Klassengedöns weg optimiert
Komplett

Mit Konstruktor macht der Kompiler alles inline.
Es wird kein zusätzliches call/ret generiert.

Kann man sic hauch schöne mit dem Disassembler anschauen.

von Oliver S. (oliverso)


Lesenswert?

Arduino Fanboy D. schrieb:
> Mit Konstruktor macht der Kompiler alles inline.

Weil
> ; 0x800102 <guard variable for Test::instance()::x>

auch für AVR thread-safe-code erzeugt wird.

Oliver

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

pointer->wald schrieb:
> static test x;

Die Initialisierung von statischen lokalen Variablen erfordert 
Thread-Synchronisation, welche sich anscheinend beim default-Konstruktor 
wegoptimieren lässt. Wenn 2 Threads gleichzeitig "inst" aufrufen muss 
sichergestellt werden, dass die "x" Variable nur exakt 1x initialisiert 
wird; da der eigene Konstruktor beliebigen Code enthalten kann, geht das 
nur über Mutexe. Dass dein Controller ggf. gar kein RTOS mit Threads 
nutzt weiß der Compiler nicht.

Die Lösung ist ganz schlicht eine Klassen-statische Variable zu 
verwenden:
1
class test {
2
  private:
3
    static test instance;
4
  public:
5
    static test& inst() {
6
        return instance;
7
    }
8
};

Der Nachteil daran ist, dass der Konstruktor immer aufgerufen wird, auch 
wennn "inst" niemals aufgerufen wird; dafür kann man den Konstruktor 
auch ggf. "constexpr" machen, was die Initialisierung noch effizienter 
macht.

Es ist auch besser eine Referenz statt eines Pointers zurückzugeben.

von hmmmm (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Ohne Konstuktor wird das ganze Klassengedöns weg optimiert
> Komplett

Frage:

Würde bedeuten dass ich ohne existierenden Konstruktor keine
zweite (oder weitere) Instanz(en) anlegen kann? Denn bei
mehreren Instanzen brauche ich ja "Klassengedöns" (?), sonst
würde ja für jede Instanz neuer Code angelegt (werden müssen)(?).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

hmmmm schrieb:
> Würde bedeuten dass ich ohne existierenden Konstruktor keine
> zweite (oder weitere) Instanz(en) anlegen kann?

Doch klar. Es ist ja doch einer vorhanden, der wird automatisch erzeugt. 
Ohne Konstruktur könntest du überhaupt keine Instanz anlegen!

hmmmm schrieb:
> Denn bei
> mehreren Instanzen brauche ich ja "Klassengedöns" (?), sonst
> würde ja für jede Instanz neuer Code angelegt (werden müssen)(?).

Code wird nur pro Klasse erzeugt, nicht pro Instanz. Keine Ahnung was 
"Klassengedöns" ist und wie da was von wegoptimiert werden kann...

von Oliver S. (oliverso)


Lesenswert?

Das klappt schon. Die Variante ohne Konstruktor ist halt nicht 
thread-safe.

Falls nicht schon vorhanden, reduziert -fno-threadsafe-statics die 
Codegröße mit Konstruktor etwas.

Oliver

von hmmmm (Gast)


Lesenswert?

Niklas G. schrieb:
> Code wird nur pro Klasse erzeugt, nicht pro Instanz.

Das ist mir schon klar. War auch bereits Inhalt meiner Frage.

Niklas G. schrieb:
> Keine Ahnung was
> "Klassengedöns" ist und wie da was von wegoptimiert werden kann...

Unser allwissender Fanboy behauptet jedenfalls sowas.

Unter seinem "Klassengedöns" habe ich verstanden dass das Handling
vorhanden ist für mehrere Instanzen ein und denselben Code zu
verwenden wie es normalerweise implementiert ist.

von pointer->wald (Gast)


Lesenswert?

Danke für die Antworten!

Der Code ist für den LPC11xx Chip.

von Oliver S. (oliverso)


Lesenswert?

hmmmm schrieb:
> Unter seinem "Klassengedöns" habe ich verstanden

Schau dir doch einfach den Assemblercode an, da siehst du, was passiert.

Oliver

von Einer K. (Gast)


Lesenswert?

Niklas G. schrieb:
> Keine Ahnung was
> "Klassengedöns" ist und wie da was von wegoptimiert werden kann...

Das asm Listing "ohne Konstuktor" zeigt, dass alles was die 
Klasse/Instanz betrifft, nicht im generierten Code erscheint.
Es wird komplett weg optimiert.
Mit Stumpf und Stiel.

Ein Programm nur mit:
1
int main( void )
2
{
3
  return 0;
4
}
Erzeugt exakt den gleichen Maschinencode.

von hmmmm (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Es wird komplett weg optimiert.
> Mit Stumpf und Stiel.

Dann erklär mal bitte wie mehrere Instanzen einer Klasse
ohne "Klassengedöns" auskommen sollen.

Gut, du kannst dich rausreden indem du dein "Klassengedöns"
nach deinem Belieben definierst.

Ich habe es nun mal so aufgefasst wie von mir beschrieben.

von Markus (Gast)


Lesenswert?

hmmmm schrieb:
> Dann erklär mal bitte wie mehrere Instanzen einer Klasse
> ohne "Klassengedöns" auskommen sollen.

Im konkreten Code kommen mehrere Instanzen nicht vor.


Niklas G. schrieb:
> Es ist auch besser eine Referenz statt eines Pointers zurückzugeben.
Kurze Zwischenfrage: Warum bietet sich hier eher eine Referenz an?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Arduino Fanboy D. schrieb:
> Es wird komplett weg optimiert.
> Mit Stumpf und Stiel.

Weil die Klasse halt nicht wirklich verwendet wird. Auch eine Klasse 
ohne expliziten Konstruktor kann "normalen" Klassen-Spezifischen-Code 
erzeugen, also z.B. eigenständige Member-Funktionen.

Markus schrieb:
> Kurze Zwischenfrage: Warum bietet sich hier eher eine Referenz an?

Ein Zeiger sagt dem Aufrufer, dass er auch Null sein kann, und er eben 
auf Null prüfen muss. Eine Referenz kann nicht Null sein, weshalb man 
dort keine Prüfung machen muss. Im konkreten Fall wird nie Null 
zurückgegeben, also kann man auch eine Referenz nehmen.

von Einer K. (Gast)


Lesenswert?

hmmmm schrieb:
> Dann erklär mal bitte wie mehrere Instanzen einer Klasse
> ohne "Klassengedöns" auskommen sollen.
Das war kein Thema im Eingangsposting.

Da dreht es sich nur um die unterschiedlichen Codegrößen, in 
Abhängigkeit von der Anwesenheit eines eigenen Konstruktors.

von hmmmm (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Das war kein Thema im Eingangsposting.

ja neee .... iss klaa

von Einer K. (Gast)


Lesenswert?

hmmmm schrieb:
> Arduino Fanboy D. schrieb:
>> Das war kein Thema im Eingangsposting.
>
> ja neee .... iss klaa

Ich beziehe mich auf das Eingangsposting und mein "Experiment" dazu.
Wenn du über irgendwas anderes schwadronieren möchtest, nur zu, aber 
halt mich bitte da raus.

von Rigó M. (rig_m)


Lesenswert?

Einfach mit -fno-threadsafe-statics kompilieren, dann kann man statische 
Klassen auf dem AVR effektiv nutzen.

Die Erklärung für diesen Switch:
Do not emit the extra code to use the routines specified in the C++ ABI 
for thread-safe initialization of local statics. You can use this option 
to reduce code size slightly in code that doesn’t need to be 
thread-safe.

https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.