Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich geb's zu, der Titel ist nicht gut gewählt, aber mir fällt nichts besseres ein. Ich wollte jetzt endlich mal von der C-Angewohnheit weg, numerische Konstanten als Makros zu definieren und dachte das enum die Lösung ist. Geht aber nicht so einfach. Mein Problem ist, das ich die Größe eines enum-Typs nicht kenne bzw. nicht festlegen kann. Beim Microsoft Compiler scheint das aber möglich zu sein. Da sich bei meinem aktuellen Projekt abzeichnet, das die Struktur, in der das vorkommt, sehr häufig benutzt werden könnte, möchte ich nicht so verschwenderisch mit Speicherplatz umgehen, oder zumindest wissen, was der Compiler da macht. Angedacht hatte ich folgende Struktur:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | enum u_action { none = 0, ins, del, style };
union u_buf {
char a[sizeof(char*)];
char * p;
};
typedef struct {
u_action action; // what happened
short mem_flg; // indicates that extra buffer is needed
int c_pos; // cursor position
int n_bytes; // number of bytes
u_buf text; // the text (if any)
u_buf style; // the style info
} undo_t;
|
Das ganze soll ein Undo-System für eine Notizzettel Verwaltung werden, womit sich der Speicherbedarf mit der Anzahl der Undo-Stufen (z.B. 100) und der Anzahl der bearbeiteten Notizen multipliziert. Aber selbst wenn sich herausstellt, das mein altertümlicher PC das locker weg steckt, wüsste ich gerne, was ich da machen kann.
|
TheDarkRose
Anmeldungsdatum: 28. Juli 2010
Beiträge: 3459
|
Einfach mal machen und wenns nicht passt später refactoren. Diese Mikrooptimierungen am Anfang kosten ziemlich viel Zeit.. Btw. egal wie klein/groß deine Datentypen sind, im struct werden dieseo sowieso auf 32 oder 64 bit aligned und blasen das ganze somit auf.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Diese Mikrooptimierungen am Anfang kosten ziemlich viel Zeit..
Ich glaube jetzt nicht, das dass schon Mikrooptimierung ist. Ich versuche eigentlich immer solche Strukturen so zu gestalten, dass sie in diese Raster passen. Daher wollte ich 'action' und ' mem_flg' in einem 32 Bit Bereich zusammenfassen. Zum Multiplikator: Meine bisher größte Datei enthält nur 3000 Einträge. Wenn ich da ein globales "Suchen und Ersetzen" mache und jeder Eintrag 100 Undo Stufen hat, reden wir von 30000. Da sind dann die in der Struktur reservierten Puffer noch nicht enthalten. Wahrscheinlich muss ich da irgendwo eine Bremse einbauen. Also der Extremfall währe, wenn ich nur ein On/Off Flag benötige, da währe es blöd dafür ein 64-Bit Integer zu reservieren wenn auch 8 Bit reichen würden. Ob das relevant ist, hängt natürlich vom Multiplikator ab.
|
TheDarkRose
Anmeldungsdatum: 28. Juli 2010
Beiträge: 3459
|
Wie gesagt, egal wie klein der Datentyp eines Members ist, er wird immer 4 oder 8 Byte (je nachdem ob 32 oder 64 bit Architektur) belegen. Zumindestens jetzt am Anfang. Du musst dein Struct schon richtig ordnen: http://www.catb.org/esr/structure-packing/ Enum hängt eben stark vom Compiler ab:
Using enumerated types instead of #defines is a good idea, if only because symbolic debuggers have those symbols available and can show them rather than raw integers. But, while enums are guaranteed to be compatible with an integral type, the C standard does not specify which underlying integral type is to be used for them. Be aware when repacking your structs that while enumerated-type variables are usually ints, this is compiler-dependent; they could be shorts, longs, or even chars by default. Your compiler may have a pragma or command-line option to force the size.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Du musst dein Struct schon richtig ordnen:
Deswegen wollte ich für den undo-action Code ja auch nur 16 Bit spendieren. Vielleicht sollte ich da auf "const short" ausweichen, wobei mir momentan aber noch nicht klar ist, wo ich das dann hinschreiben muss. Davon abgesehen bin ich auch am überlegen, die Struktur noch einmal zu ändern. Ursprünglich wollte einzelne Zeichen dort ablegen, wo sonst der Zeiger auf einen String gespeichert ist, weil das weit über 90% aller Fälle abdeckt. Jetzt bin ich aber am überlegen, ob der Programmcode nicht merkbar einfacher wird, und vielleicht auch schneller, wenn man das trennt. Schließlich muss ich den Speicher ja auch irgendwann wieder freigeben und dazu alle Einträge absuchen. Und dann auch noch deswegen: The riskiest form of packing is to use unions. If you know that certain fields in your structure are never used in combination with certain other fields, consider using a union to make them share storage.
obwohl die genannte Bedingung erfüllt ist.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb:
Angedacht hatte ich folgende Struktur:
| enum u_action { none = 0, ins, del, style };
union u_buf {
char a[sizeof(char*)];
char * p;
};
|
Was ist denn der Sinn der Definition in Zeile 4? Du deklariest ein char-Array mit der Länge eines Zeigers auf der Plattform? Warum nimmst Du dann nicht gleich einen void* ?
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Warum nimmst Du dann nicht gleich einen void*?
Das ist wieder mal ein Fall von zu kurz bzw. nicht zuende gedacht. Das werde ich anpassen. Den Sinn hatte ich aber glaube ich schon erwähnt. Da es sich meistens um einzelne ASCII Zeichen oder um bis zu 3 Bytes lange UTF-8 Strings handelt, wollte ich die aufwändige Speicherreservierung nur machen, wenn unbedingt nötig. Kurze Strings kann ich ja direkt dort speichern. Im Übrigen funktioniert das Ganze beängstigend gut. Es ist übrigens auch sehr angenehm, wenn der Debugger anstelle einfacher Zahlen direkt deren Bedeutung anzeigt. Von daher werde ich die enum's wohl behalten.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb: Warum nimmst Du dann nicht gleich einen void*?
Das ist wieder mal ein Fall von zu kurz bzw. nicht zuende gedacht. Das werde ich anpassen. Den Sinn hatte ich aber glaube ich schon erwähnt. Da es sich meistens um einzelne ASCII Zeichen oder um bis zu 3 Bytes lange UTF-8 Strings handelt, wollte ich die aufwändige Speicherreservierung nur machen, wenn unbedingt nötig. Kurze Strings kann ich ja direkt dort speichern.
Aber dann würde ich es auch genau so deklarieren, als drei Zeichen langes char Array. Der sizeof() dort ist völlig irreführend. Ein sprechender (also längerer Bezeichner) würde die Sache auch deutlich besser machen. Das würde ich Dir generell raten, deutlichere Bezeichner zu verwenden. Die kosten kein Brot und die meisten IDE's, die es heute so gibt, haben auch automatische Vervollständigung usw.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ja, ich gebe zu, dass ich etwas schreibfaul bin. Ich arbeite daran. Die Idee bei der Verwendung von sizeof() war, dass ich aber auch zu faul bin das Array bei einer 64 Bit Umgebung manuell anpassen zu müssen. Und das mit dem 3 Byte UTF-8 String war nur ein Beispiel. Tatsächlich soll da alles rein, was geht, da wird die Auswirkung jedes Tastaturanschlags protokolliert, also auch Cut & Paste.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb: Ja, ich gebe zu, dass ich etwas schreibfaul bin. Ich arbeite daran.
👍
Die Idee bei der Verwendung von sizeof() war, dass ich aber auch zu faul bin das Array bei einer 64 Bit Umgebung manuell anpassen zu müssen.
Kann es sein, dass Du da einem Denkfehler unterlegen bist? Ein char ist immer 1 Byte, egal ob Du auf 32 oder 64 Bit-Architektur bist. Wenn Du also ein Array mit drei Byte deklarieren willst, dann kannst Du da die 3 fest eintragen.
Und das mit dem 3 Byte UTF-8 String war nur ein Beispiel. Tatsächlich soll da alles rein, was geht, da wird die Auswirkung jedes Tastaturanschlags protokolliert, also auch Cut & Paste.
In drei Bytes? Das ist dann aber nur das Kommando, nicht die Daten, die kopiert wurden, oder?
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
In drei Bytes? Das ist dann aber nur das Kommando, nicht die Daten, die kopiert wurden, oder?
Ok, dann noch einmal in epischer Breite und zusammenhängend. Das Kommando selbst ist in u_action abgespeichert (ins, del (delete wollte der Compiler nicht 😉 ) und style).
Das Editor Objekt sagt mir wieviele Bytes (nicht Zeichen!) eingefügt, gelöscht oder resyled wurden. Diese Daten sollen in "text" gespeichert werden, entweder direkt oder in zusätzlich zu reservierendem Speicher. Der Code, der das macht sieht so aus (habe aus gegebenem Anlass noch etwas Kommentar hinzugefügt):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | // Copy a string of n bytes to the undo buffer and allocate aditional
// memory if necessary.
//
// ustr store the undo string in this structure (union).
// str pointer into the editors text
// n number of modified bytes (!)
//
// Returns 1 if memory was allocated
//
// (static)
int Undo::undo_string( u_buf * ustr, const char * str, int n ) {
char * stemp;
int flag = 0;
if( n < sizeof( u_buf ) ) {
strncpy( ustr->a, str, n );
ustr->a[n] = '\0';
} else {
stemp = new char[n + 1];
strncpy( stemp, str, n );
stemp[n] = '\0';
ustr->p = stemp;
flag = 1;
}
return flag;
}
|
Der Aufruf erfolgt dann etwa so:
| undo_t ubuf;
...
ubuf.mem_flg = Undo::undo_string( &ubuf.text, s, nDeleted );
nbox->undo->input( &ubuf ); // Eintrag in den Undo Puffer
...
|
Ich hoffe das meine Idee damit klar wird.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb:
Das Kommando selbst ist in u_action abgespeichert (ins, del (delete wollte der Compiler nicht 😉 ) und style).
Ja, delete ist ein Schlüsselwort.
Das Editor Objekt sagt mir wieviele Bytes (nicht Zeichen!) eingefügt, gelöscht oder resyled wurden. Diese Daten sollen in "text" gespeichert werden, entweder direkt oder in zusätzlich zu reservierendem Speicher. Der Code, der das macht sieht so aus (habe aus gegebenem Anlass noch etwas Kommentar hinzugefügt):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | // Copy a string of n bytes to the undo buffer and allocate aditional
// memory if necessary.
//
// ustr store the undo string in this structure (union).
// str pointer into the editors text
// n number of modified bytes (!)
//
// Returns 1 if memory was allocated
//
// (static)
int Undo::undo_string( u_buf * ustr, const char * str, int n ) {
char * stemp;
int flag = 0;
if( n < sizeof( u_buf ) ) {
strncpy( ustr->a, str, n );
ustr->a[n] = '\0';
} else {
stemp = new char[n + 1];
strncpy( stemp, str, n );
stemp[n] = '\0';
ustr->p = stemp;
flag = 1;
}
return flag;
}
|
Der Rückgabewert ist redundant, denn Du kannst am Wert in ustr->p erkennen, ob allokiert wurde (!= NULL). Du müsstest halt die NULL reinschreiben. Der Ansatz ist m.E. besser, weil Du dann den Zustand in den Daten speicherst und nicht getrennt davon. Und warum ist Undo::undo_string() keine Methode von u_buf ? Da hinein gehört sie m.E., denn sie behandelt ja nur Zustand von dieser Klasse. Übrigens, wenn Du tatsächlich Bytes und keine Zeichen speichern willst, dann würde ich mindestens unsigned char nehmen.
Der Aufruf erfolgt dann etwa so:
| undo_t ubuf;
...
ubuf.mem_flg = Undo::undo_string( &ubuf.text, s, nDeleted );
nbox->undo->input( &ubuf ); // Eintrag in den Undo Puffer
...
|
Was macht denn die Funktion input() ? Wird ubuf da noch einmal kopiert?
Ich hoffe das meine Idee damit klar wird.
Ja. Danke!
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Und warum ist Undo::undo_string() keine Methode von u_buf?
Weil u_buf (union) nur ein Teil der gesamten Undo Datenstruktur ist.
Der Rückgabewert ist redundant, denn Du kannst am Wert in ustr->p erkennen
Leider nicht, denn physikalisch ist das ja der gleiche Speicherplatz (wg union). Ich kann nicht erkennen ob das ein Zeiger oder schon der Text ist.
Übrigens, wenn Du tatsächlich Bytes und keine Zeichen speichern willst, dann würde ich mindestens unsigned char nehmen.
Das währe wahrscheinlich sauberer, aber dann müsste ich casten wenn der Text wieder im Editor eingefügt werden soll. Auf den Editor habe ich keinen Einfluss, der gehört zum FLTK Paket und ich bin froh, dass ich den einfach so in meine Projekte einbauen kann. Der Editor nennt mir tatsächlich nur die Anzahl der Bytes. Ich kann dann nicht wissen ob das n ASCII Zeichen oder ein n Byte langer UTF-8 String ist.
Was macht denn die Funktion input()? Wird ubuf da noch einmal kopiert?
Ja leider. Das geht alles in eine Art Ringpuffer. Aber vielleicht kann ich das so abändern, das dass Undo Objekt einen Zeiger auf den nächsten Puffereintrag bereitstellen kann ohne das meine Indices durcheinander kommen.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb:
Der Rückgabewert ist redundant, denn Du kannst am Wert in ustr->p erkennen
Leider nicht, denn physikalisch ist das ja der gleiche Speicherplatz (wg union). Ich kann nicht erkennen ob das ein Zeiger oder schon der Text ist.
Ach ja, es ist ja eine union und kein struct . Das hatte ich einen Moment vergessen. ☹ Ich würde das vermutlich eher mit zwei bis drei Klassen machen und dann abhängig von der Länge eine Instanz des entsprechenden Typs erzeugen. Das ist mehr OO. Unions sind so ein C-Konstrukt, das immer ein wenig gefährlich ist (so wie ein void* , den man auf alles mögliche zeigen lässt).
Übrigens, wenn Du tatsächlich Bytes und keine Zeichen speichern willst, dann würde ich mindestens unsigned char nehmen.
Das währe wahrscheinlich sauberer, aber dann müsste ich casten wenn der Text wieder im Editor eingefügt werden soll. Auf den Editor habe ich keinen Einfluss, der gehört zum FLTK Paket und ich bin froh, dass ich den einfach so in meine Projekte einbauen kann. Der Editor nennt mir tatsächlich nur die Anzahl der Bytes. Ich kann dann nicht wissen ob das n ASCII Zeichen oder ein n Byte langer UTF-8 String ist.
Und dafür verwendet der Editor char ? Das ist aber auch seltsam, denn für UTF-8 braucht man ja auf jeden Fall alle Werte von 0 bis 255. Naja, irgendeinen Grund wird es dafür geben... Dann ist es natürlich sinnvoll den selben Typ zu verwenden.
Was macht denn die Funktion input()? Wird ubuf da noch einmal kopiert?
Ja leider. Das geht alles in eine Art Ringpuffer. Aber vielleicht kann ich das so abändern, das dass Undo Objekt einen Zeiger auf den nächsten Puffereintrag bereitstellen kann ohne das meine Indices durcheinander kommen.
Ja, oder das ganze Teil gleich auf den Heap legen (und nicht auf den Stack wie in der gezeigten Methode) und auto_ptr bzw. die Nachfolger verwenden.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Und dafür verwendet der Editor char? Das ist aber auch seltsam, denn für UTF-8 braucht man ja auf jeden Fall alle Werte von 0 bis 255. Naja, irgendeinen Grund wird es dafür geben...
Nun ja, das muss man wohl historisch sehen. Das Editor Objekt basiert wohl auf einem ältern Editor namens "nedit", den die FLTK Leute wohl verwenden durften, und der dann an neue Randbedingungen angepasst wurde. Eigentlich sind das dann 3 Objekte also Fl_Text_Editor, der auf Fl_Text_Display aufbaut und die sich dann beide wiederum auf Fl_Text_Buffer stützen. Zum Einfügen wird dann void Fl_Text_Buffer::insert ( int pos, const char * text ) benutzt. Der ursprüngliche Editor kannte wohl noch kein Unicode und auch keine proportionale Schrifttypen.
Ich würde das vermutlich eher mit zwei bis drei Klassen machen und dann abhängig von der Länge eine Instanz des entsprechenden Typs erzeugen. Das ist mehr OO. Unions sind so ein C-Konstrukt, das immer ein wenig gefährlich ist (so wie ein void*, den man auf alles mögliche zeigen lässt).
Ja, von da her sind die Beispiele von FLTK wohl nicht unbedingt als Lehrbeispiele für OOP geeignet. Da gibt es viele globale Funktionen und vor allen Dingen auch "void*". Für mich als C++ und OOP Anfänger ist das schon etwas problematisch. Aber andererseits bin ich auch immer wieder begeistert von der Geschwindigkeit der auf FLTK basierenden Anwendungen (im Vergleich zu GTK+), was allerdings nur auf älteren PCs deutlich wird. Und ich bin in Sachen C++ alles andere als sattelfest, weswegen ich noch viel experimentiere.
Ja, oder das ganze Teil gleich auf den Heap legen (und nicht auf den Stack wie in der gezeigten Methode) und auto_ptr bzw. die Nachfolger verwenden.
Das habe ich jetzt nicht wirklich verstanden. Also das "Ringpuffer-ähnliche" Objekt wird mittels "new" mit fester Größe auf dem Heap angelegt. Bisher ist das allerdings nur dem Editor-Fenster zugeordnet. Später könnte das auf alle Einträge des Notiz Programms ausgeweitet werden was dann aber weitere organisatorische Probleme bereitet. Ich muss hier wohl noch mal erwähnen, dass ich mich mit C++ immer noch etwas schwer tue. Insbesondere hatte ich noch keine Zeit mich eingehend mit der STL zu befassen. Möglicherweise könnte ich einige meiner Probleme darauf abwälzen. Allerdings müsste ich das dann auch verstanden haben, und soweit bin ich noch nicht. Ich denke vorwiegend noch in "Ansi C" und nicht in Templates.
|