Forum: Compiler & IDEs avr-g++: Zugriff auf Pseudoobjekt per extern


von A. N. (netbandit)


Lesenswert?

Hallo,

ich habe in C++ einige Hardwarezugriffe auf einen AtMega128 in Klassen 
verteilt. Diese Klassen enthalten nur Methoden und keine Daten. Leider 
ist es so, dass unter C++ jede Klasse, auch dann wenn Sie praktisch 
keine Daten enthält, mindestens 1 Byte im Speicher belegt (um Objekte 
dieser Klasse in einem Array unterscheiden zu können).

Das mag auf einem PC sicher weniger schlimm sein, aber auf einem µC sehe 
ich es absolut nicht ein, pro Klasse ein Byte spendieren zu müssen, 
obwohl die Klasse nur Methoden zur besseren Strukturierung und 
Abstraktion der Hardware enthalten. Vor allem bei einer etwas größeren 
Verschachtelung dieser führt das schnell zu viel Speicherverbrauch der 
theoretisch gar nicht nötig ist.

Also habe ich ausprobiert, anstatt ein Objekt dieser Hardware-Klasse 
anzulegen einfach ein Objekt über "extern" zu deklarieren. Dieses Objekt 
wird allerdings in keiner anderen später gelinkten Datei wirklich 
definiert. Ich sage dem Compiler also: "Hey da gibt es ein Objekt, ich 
weiß nicht wo, aber es sieht so und so aus."

Siehe da, ich habe auf alle Methoden dieser Klasse Zugriff und auch auf 
die Methoden der Unterklassen und das ohne, dass Speicherplatz dafür 
reserviert werden muss.

Die Frage die sich nun stellt: Ist dies eher ein schmutziger Workaround 
im avr-g++ oder, der womöglich in späteren Versionen nicht mehr 
funktionieren wird oder ist so etwas in C++ grundsätzlich zulässig und 
kann dann auch so verwendet werden?

Beispielcode:
1
class CTest {
2
  public:
3
    void tudies (void) {
4
      return;
5
    }
6
};
7
8
extern CTest T;
9
10
void main(void) {
11
12
  T.tudies();
13
14
  while (1) {
15
  }
16
  return;
17
}

von P. S. (Gast)


Lesenswert?

Du koenntest auch NULL auf den Klassentyp casten und darueber zugreifen 
- aber das ist genauso Pfusch ;-)

Was du eigentlich willst, ist, die Methoden als static deklarieren und 
sie dann per KlassenName::MethodenName( ) aufrufen.

Oder du machst das mit Namespaces und Funktionen - so viele Namespaces, 
wie du dann braeuchtest, wuerde ich aber nicht einsetzen wollen.

Btw, geschweifte Klammern gehoeren IMO uebereinander - sie markieren 
Anfang und Ende eines Blocks und sollten den Block leicht erkennbar 
machen.

von A. N. (netbandit)


Lesenswert?

Hallo Peter,

die Idee mit dem Pointer ist eigentlich super. Das dürfte auch auf 
andere Compiler übertragbar sein und ist absolut kein Problem solange 
eben kein Speicherzugriff stattfindet.
Bei dem "extern" Trick besteht halt noch der Vorteil, dass der Linker 
meckert sobald ein Speicherzugriff stattfindet, weil er dann die 
dazugehörige Speicherstelle nicht finden kann.

Ich habe das gerade mal beim Visual C++ 2008 Compiler ausprobiert: Die 
extern-Variante nimmt er nicht (Linker kann das Symbol nicht finden), 
die mit dem Null-Pointer schon.

Die Variante mit dem static geht nicht, da in der Klasse weitere Klassen 
(ohne Daten-Member)geschachtelt sind. Diese "objekte" müssten dann auch 
static sein und würden in diesem Moment ebenfalls ja 1 Byte belegen. Ich 
hätte also nur das eine Byte der obersten Klasse gespart.

Namespace funktioniert nicht, da es sich um Templateklassen handelt und 
eine solche Funktionalität meiner Meinung nach nicht mit Namespaces 
nachgestellt werden kann.

Und das mit den Klammern ist eben Ansichtssache ;-)

von P. S. (Gast)


Lesenswert?

Ich denke ich habe dich verstanden - aber so richtig begeistern tut es 
mich nicht ;-)

von A. N. (netbandit)


Lesenswert?

Ja begeistern tun mich beide Lösungen auch nicht. Die eine scheint nicht 
portierbar zu sein und wer weiß wie es mit der nächsten Version des 
avr-g++ aussieht. Die andere ist zwar portierbar birgt aber das Risiko 
auf einen ungewollten Speicherzugriff (falls man aus Versehen doch mal 
ein Datenelement in einer der Klassen definiert).

Das leere Klassen immer ein Byte groß sind ist ja eine reine 
Definitionssache des Compilers um eventuelle andere Probleme umgehen zu 
können.

Mir wäre es am liebsten, wenn man den Compiler anweisen könnte für 
solche Klassen keinen sinnlosen Speicher zu reservieren. Das sind 
immerhin einige etliche Byte die effektiv nicht genutzt werden... *Kopf 
schüttel*

von yalu (Gast)


Lesenswert?

Peter Stegemann schrieb:
> Was du eigentlich willst, ist, die Methoden als static deklarieren und
> sie dann per KlassenName::MethodenName( ) aufrufen.

Genau, so hätte ich's auch gemacht. Ein Objekt von einer leeren Klasse
zu instanziieren verwirrt doch mehr als es nützt. Der Compiler sollte
das nicht mit 1 Byte bestrafen, sondern mit 1 GByte ;-)

A. N. schrieb:
> Die Variante mit dem static geht nicht, da in der Klasse weitere
> Klassen (ohne Daten-Member)geschachtelt sind.

Das verstehe ich nicht. Man kann doch wunderbar Methoden auch in
verschachtelten Klassen aufrufen:
1
class A {
2
  public:
3
    class B {
4
      public:
5
        static void mb() {}
6
    };
7
    static void ma() { B::mb(); }
8
};
9
10
int main() {
11
  A::ma();
12
  A::B::mb();
13
  return 0;
14
}

von A. N. (netbandit)


Lesenswert?

Naja, ganz so simpel ist die Struktur dahinter nicht. Das ganze 
entspricht eher diesem Beispiel hier und da sehe ich keine Möglichkeit 
ohne "Pseudoobjekt" sinnvoll auf die Methoden zugreifen zu können.
1
template<uint8_t N>
2
class A {
3
  public:
4
    static void ma() {
5
      // mach was mit N
6
      return;
7
    }
8
};
9
10
class B {
11
  public:
12
    A<0> a0;
13
    A<1> a1;
14
    A<2> a2;
15
    A<3> a3;
16
    A<4> a4;
17
    A<5> a5;
18
    static void mb() {
19
      return;
20
    }
21
};

von yalu (Gast)


Lesenswert?

Also möchtest du eher so etwas:
1
#include <iostream>
2
#include <stdint.h>
3
4
template<uint8_t N>
5
class A {
6
  public:
7
    static void ma() {
8
      std::cout << "Ich gehöre zur Klasse A<" <<(int)N << ">." << std::endl;
9
      return;
10
    }
11
};
12
13
class B {
14
  public:
15
    typedef A<0> A0;
16
    typedef A<1> A1;
17
    typedef A<2> A2;
18
    typedef A<3> A3;
19
    typedef A<4> A4;
20
    typedef A<5> A5;
21
    static void mb() {
22
      A4::ma();
23
      // oder gleich so, dann können die obigen Typedefs enfallen:
24
      A<4>::ma();
25
      return;
26
    }
27
};
28
29
int main() {
30
  B::mb();
31
  A<5>::ma();
32
  B::A5::ma();
33
  return 0;
34
}

Nicht? Wie sieht denn ein typischer Methodenaufruf bei dir aus? Ich kann
mir im Moment keinen Fall vorstellen, wo man leere Objekte wirklich
bräuchte.

von A. N. (netbandit)


Lesenswert?

Die Idee mit den typedefs ist sehr gut, so werde ich es wohl machen... 
Danke yalu!

Nun zu deiner Frage: Wenn die eigentliche Variation der Klassen nicht in 
Datenobjekten stecken sondern im Templateparameter N, dann besitzt die 
Klasse keine Datenobjekte sondern nur Methoden, es ist also eine leere 
Klasse.

von yalu (Gast)


Lesenswert?

> ... es ist also eine leere Klasse.

Dass Klassen ohne Datenelemente sinnvoll sein können, ist schon klar.
Ich kann mir nur nicht vorstellen, dass es Fälle gibt, wo es sinnvoll
oder sogar notwendig ist, von solchen Klassen Objekte zu instanziieren,
da ein Objekt ohne Daten meiner Meinung nach nicht mehr ist/kann als
seine Klasse.

Nachdem du die "Wegoptimierung" der Objekte a0 bis a5 durch die Typedefs
akzeptiert hast, hat sich das Thema aber wohl weitgehend erledigt.

von A. N. (netbandit)


Lesenswert?

Naja, es ist eben der nächste logische Schritt: Man programmiert eine 
Klasse mit all seinen Methoden und um diese zu benutzen legt man eben 
ein Objekt dieser an. Der einzige Grund, warum man das nicht machen kann 
ist doch die Definition in den C++ Standard, dass jedes Objekt 
mindestens ein Byte groß ist, auch wenn dieses gar nicht im Speicher 
existiert da es keinen Speicher belegt. Wäre das nicht so definiert, 
dann spräche doch nichts dagegen mit leeren Klassen genauso zu verfahren 
als wie mit vollen Klassen.

Ich meine das sieht ja auch im Quellcode komisch aus, wenn man auf die 
einen Methoden über den "." Operator zugreift und auf andere Methoden 
über ein "::". Dem Programmierer, der am Ende mit den Klassen umgehen 
muss, ist es doch egal ob da Daten drin gespeichert werden und daher 
Objekte angelegt werden müssen oder ob es leere Klassen sind, die daher 
ohne Objekte verwendet werden.

Das verletzt ja auch das Prinzip der Kapselung: Einmal ein Projekt mit 
solchen Klassen realisiert, kann ich mich nicht mehr im Nachhinein 
entscheiden doch noch ein Datenobjekt in eine der Klassen zu 
integrieren, dann müsste man nämlich doch wieder ein Objekt dieser einen 
Klasse anlegen und damit den ganzen Quellcode umbauen. Könnte man 
ungestraft ein Objekt einer leeren Klasse anlegen, dann wäre das gar 
kein Thema. Aber was soll man machen... Standard ist Standard.

Danke nochmal für deine Lösung... manchmal sind die einfachsten 
Varianten die auf die man gar nicht kommt.

von A. N. (netbandit)


Lesenswert?

Oh, ich merke gerade, dass ich das doch nicht so realisieren kann. Ich 
nutze in den Klassen sehr massiv überladene Operatoren und die dürfen 
nicht static sein.

Also bleibt nichts anderes übrig als doch mit einem Pseudoobjekt zu 
arbeiten.

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.