Forum: Compiler & IDEs Kapselung in C


von Andreas G. (andy1988)


Lesenswert?

Hallo,
ich habe eine generelle Frage zum Programmieren in C.

Ich bin sonst nur verwöhntes OOP Kind. Hab bisher fast nie etwas mit 
prozedualer Programmierung zu tun gehabt, ausser ein wenig PHP Gefrickel 
in meinen Anfangszeiten.
Selbst in C++ hab ich bisher relativ wenig gemacht. Langsam komm ich 
jedoch auf den Geschmack von Hardwarenäherem Zeug.
Mir gefällt der OOP-Ansatz auch wesentlich besser, aber auf 
Mikrocontrollern steh ich dem Ganzen etwas skeptisch gegenüber.

Ich stehe jetzt vor dem Problem, dass ich ein Programm habe, dass 
mehrere Queues benötigt. Eine Queue implementieren ist jetzt nicht das 
Ding. Wie gesagt: EINE.
Was macht man nun, wenn ich mehrere Queues benötige? Ich brauche also 
irgendwie Instanzen von dieser Queue.

Meine erste Idee war einfach einen Pointer auf den anfang eines 
allokierten Speicherbereis bei jeder Funktion mitzuliefern, aber das 
ganze ist auch blöd, weil ich das Ding 1. ständig überall mitführen muss 
und 2. unter Umständen noch andere Daten wie die größe oder Schreib- und 
Lesezeiger für einen Ringpuffer benötige. Irgendwie müsste ich die Daten 
Kapseln.
Bei einem Pointer auf eine struct kann ich zwar die Metadaten wie die 
Größe usw. komfortabel speichern, muss den Pointer aber immernoch von 
einer Ecke in die nächste schieben.

Was macht man da nu?
Wahrscheinlich werde ich zumindest nichtt um den Pointer auf die struct 
herumkommen.

Danke für die Hilfe,
Andreas

von Bernhard M. (boregard)


Lesenswert?

Ja, das ist das Standardverfahren in C. Viele (Programmierer, APIs) 
verstecken diesen Pointer noch indem sie ihn evtl. als void* anlegen 
(mit typedef) und nennen das dann "Handle".

Na, und wie machts den C++?

Im Prinzip genauso, die Erweiterung ist, daß jede Instanz einen "this" 
pointer hat, der auf die eigenen Daten zeigt. In C++ wird im Prinzip 
dieser Zeiger als erster Parameter jeder Methode übergeben, dies ist 
aber ein versteckter Parameter, d.h. in C++ sieht man ihn nicht, aber 
auf Assemblerebene ist er da....

von Stefan E. (sternst)


Lesenswert?

Andreas Galauner wrote:

> Was macht man da nu?

Das, was du schon angedeutet hast. Alle Daten, die zu einer Queue 
gehören (Puffer, Indizes, etc), in eine Struktur packen (das ist dann 
quasi dein Objekt) und den Funktionen jeweils einen Pointer auf solch 
eine Struktur mitgeben.

> Wahrscheinlich werde ich zumindest nichtt um den Pointer auf die struct
> herumkommen.

Genauso wenig, wie du bei OOP um das konkrete Objekt herumkommst.

OOP:
1
ObjTyp Objekt;
2
3
Objekt.MethodeA(...);
4
Objekt.MethodeB(...);

C:
1
ObjTyp Objekt;
2
3
MethodeA(&Objekt,...);
4
MethodeB(&Objekt,...);

Bei OOP hast du nur bei der Implementierung der Methoden eine etwas 
kürzere Schreibweise beim Datenzugriff (jedenfalls meistens, je nach 
Sprache).

OOP:
1
RetTyp ObjTyp:MethodeA (...) {
2
3
    Member1 = 1;
4
    Member2 = 2;

C:
1
RetTyp MethodeA (ObjTyp *this, ...) {
2
3
    this->Member1 = 1;
4
    this->Member2 = 2;


Du siehst, die Unterschiede sind bei relativ simplen Objekten nicht sooo 
groß.

von P. S. (Gast)


Lesenswert?

Noch einfacher ist natuerlich, man nimmt einfach C++. Nur auf virtuals 
musst du verzichten, die funktionieren nach meiner Erfahrung nicht. Wenn 
du ohne nicht auskommst, musst du eben Funktionspointer nehmen.

Sorgen wuerde ich mir eher ueber Speicherfragmentierung bei Verwendung 
von dynamischen Strukturen machen...

von Andreas G. (andy1988)


Lesenswert?

Hm, naja.
Ich hab damit größeres vor. Evtl. nehm ich doch C++. Eine 
Speicherverwaltung will ich eh implementieren. Das ganze soll ein 
Experiment werden, bei dem ich mal versuche nen kleineren 
Betriebssystemkern zu implementieren.
Ja, ich weiß. Das gibt es schon. Das bringt mich aber insofern nicht 
weiter, da ich das ganze für ein Fach im Studium machen möchte. Deswegen 
die ganze Arbeit.

Da ich da jedem "Prozess", der vom Scheduler verwaltet wird, die 
Möglichkeit geben möchte Speicher, auch auf dem Heap, nicht nur auf dem 
Stack, zu allokieren, muss ich da irgendwie getrennte Adressräume 
reinbekommen. Ist ja alles möglich. Zumindest in C++ kann ich das schön 
mit dem new und delete Operator überwachen und verwalten. Jeder Prozess 
bekommt eine bestimmte Menge an Arbeitsspeicher zugewiesen. Ist der 
voll, ist das halt so. Da hab ich dann als Entwickler drauf zu achten. 
Die Adressräume sind also in der Größe statisch, auch, wenn kein 
Speicher benutzt wird. Das ganze ohne MMU dynamisch zu machen ist 
einfach zu viel für nen kleinen AVR.
Hab nur gedacht, dass es in C vielleicht angebrachter wäre sowas zu 
machen.

Ich will das ganze auf nem AVR machen, da ich gedacht habe, dass es 
einfacher sei den Kram zu implemtieren, als auf der x86 Architektur. Nur 
frag ich mich langsam, ob das so richtig ist :D
Um nen externen SRAM werd ich wahrscheinlich auch nicht rumkommen. 
Wieder mehr Aufwand.
Verglichen mit dem ganzen x86 spezifischen Kram sollte sich das aber 
wieder relativieren. Protected/Real mode, VESA implementierung für ne 
vernünftige Grafikausgabe, GDT und IDT und der ganze Rattenschwanz dazu.

Ich glaub ich fang einfach mal in C++ an und schuster mir ne 
Speicherverwaltung. Dann werd ich sehen, wie gut oder schlecht das geht 
:D

Aber danke schonmal für die Tipps.

von daniel (Gast)


Lesenswert?

@  Andreas Galauner

da hast du dir etwas interessantes vorgenommen.

>Ich glaub ich fang einfach mal in C++ an und schuster mir ne
>Speicherverwaltung. Dann werd ich sehen, wie gut oder schlecht das geht.

Würde ich vielleicht auch so machen. Viele Featers von C++
wirst eh nicht brauchen. Nachträglich in C zu übertragen
wäre auch keine grosse Sache.

Berichte mal ab und zu wie du vorankommst.

grüsse, daniel

von Andreas G. (andy1988)


Lesenswert?

Interessant auf jeden Fall. Ich werde mich, soweit zeitlich möglich (mir 
sitzt da leider sone fiese Matheprüfung im Nacken), erstmal versuchen 
mir über die Speicherverwaltung Gedanken zu machen. ich hab da in meiner 
Studentenbude ein hübsches Buch über den Linux Kernel. Mal sehen 
inwieweit mich das weiterbringt ohne MMU und meiner Vereinfachung.
Falls jemand interessante Verbesserungsvorschläge oder weitere Ideen 
hat, kann er die gerne einbringen.

Ich will halt erstmal für mich lernen, daher kann ich nur daraus 
profitieren ;)

So... jetzt widme ich mich wieder meiner Zigarre und meinem "Lecker 
Bierchen" von Horst Lichter ;)
Zum wohlsein!

von mgiaco (Gast)


Lesenswert?

Also ohne Vererbung ist Kapselung einfach. Kleines Beispiel eine 
Saturation.

saturation.h
1
#ifndef SATURATION_H_
2
#define SATURATION_H_
3
4
/* "class" Saturation */
5
typedef struct SaturationTag Saturation;
6
7
struct SaturationTag {
8
  float max;
9
  float min;
10
  boolean saturate;
11
};
12
13
extern void Saturation_ctor(Saturation *me, float max, float min);
14
extern float Saturation_saturate(Saturation *me, float in);
15
extern boolean Saturation_isSaturated(Saturation *me);
16
17
#endif /* SATURATION_H_ */


saturation.c
1
#include "types.h"
2
#include "Saturation.h"
3
4
void Saturation_ctor(Saturation *me, float max, float min) {
5
  me->max = max;
6
  me->min = min;
7
}
8
9
float Saturation_saturate(Saturation *me, float in) {
10
  if (in > me->max) {
11
    me->saturate = TRUE;
12
    return me->max;
13
  } else
14
    me->saturate = FALSE;
15
16
  if (in < me->min) {
17
    me->saturate = TRUE;
18
    return me->min;
19
  } else
20
    me->saturate = FALSE;
21
22
  return in;
23
}
24
25
boolean Saturation_isSaturated(Saturation *me) {
26
  return me->saturate;
27
}

Anwenden:

static Saturation sat1;
static Saturation sat2;

Saturation_ctor(sat1, 10, -10);
Saturation_ctor(sat1, 5, 0);

usw...

So kannst du auch deine Queue implementieren und X mal verwenden.

Wenn du auch Vererbung brauchst schau mal hier:

http://www.emilmont.net/doku.php?id=c:object_oriented_c

oder besser

http://www.state-machine.com/devzone/cookbook.htm

ganz unten C+

mfg mgiaco

von Andreas G. (andy1988)


Lesenswert?

Hmm, das is ja mal voll gut!
Hab erstmal mit C++ angefangen. Um die Vererbung rumzukommen ist gar 
nicht so einfach. Vielleicht steig ich dann doch um.

Bin grade dabei mal n grundlegendes Klassendesign zu implementieren. 
Port- und Registerzugriff funktioniert schon. Darauf aufbauend 
implementier ich ne Debugging Konsole übers USART, womit ich mir n paar 
Statistiken und RAM Dumps usw. holen kann.

Mein selbstgebauter JTAG Adapter funktioniert nicht und ich hab jetzt 
keinen Bock nach dem Fehler zu suchen ;)

von daniel (Gast)


Lesenswert?

um die virtuelle Funktionen nachzubilden, kann man zum Trick greifen
1
struct Counter {
2
   struct Operations * ops;
3
   int cnt;
4
}; 
5
6
// die aktuellen implementierungen der methoden von A
7
void increment(const struct A * self) { self->cnt++; }
8
void decrement(const struct A * self) { self->cnt--; }
9
10
// ersetze implementierung
11
void fancy_increment(const struct A * self) { self->cnt+=2; }
12
13
// das ist quasi ein singleton mit allen methoden von A
14
static struct AOperations {
15
void (*inc)(const struct A *);
16
void (*dec)(const struct A *);
17
} AOps = {increment, decrement};
18
19
void AConstr(const A * self) {
20
    self->cnt = 0;
21
    self->ops = &AOps;
22
}
23
24
int main() {
25
    struct a,b;
26
    b->ops->inc = fancy_increment;
27
    AConstr(&a);
28
    AConstr(&b);
29
    a->ops->inc(&a);
30
    b->ops->inc(&b);
31
}

virutelle Methode ist einfach ein Wechsel der Implementierung

grüsse, daniel

von daniel (Gast)


Lesenswert?

A sollte Counter heissen!

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.