Mit Hilfe von PROGMEM bzw. __flash kann ich eine const Variable im Flash ablegen. In Funktionen kann ich jeweils prüft, ob in der Variable ein vorher erzeugtes Pattern vorhanden ist. Für den Vergleich benötige ich entweder memcmp oder memcmpf.
Ich möchte nun ein C++ Funktionstemplate erzeugen, um die Adresse, Größe und den Speicherbereich (flash oder RAM) automatisch zu erkennen. Das sollte in etwa so aussehen:
1
template<typenameC>
2
boolIdentical(uint8_t*buffer,C&data){
3
return0==memcmp(buffer,&data,sizeof(data));
4
}
5
template<typenameC>
6
boolIdentical(uint8_t*buffer,__flashC&data){
7
return0==memcmpf(buffer,&data,sizeof(data));
8
}
9
__flashintA;
10
intB;
11
boolfoo(){
12
uint8_tX[2]={...};
13
if(Identical(&x,A)){
14
return1;
15
}elseif(Identical(&x,B)){
16
return2;
17
}
18
return0;
19
}
Die Realität ist komplexer, aber die Idee sollte damit beschrieben sein.
Leider funktioniert die Überladung nicht. Der Compiler erkennt keinen unterschiedlichen Type wenn das __flash Attribut angegeben ist oder nicht.
Gibt es einen Weg den Compiler hier zu überzeugen, dass das __flash ein Typbestandteil ist? Die Alternative, hier verschiedenen Funktionsnamen zu verwenden ist mir bewusst, aber unschön.
__flash deklariert keinen neuen Typ, daher kann man dadurch kein anderes Template aufrufen.
In der Arduino-Bibliothek behilft man sich mit einem Trick, indem man eine neue Klasse (und damit einen neuen Typ) namens __FlashStringHelper deklariert und dann ein Makro _F definiert, das einen gewöhnlichen C-String (Zeiger auf Zeichenkette) im Flash in einen Zeiger auf diese neue Klasse "umformt":
__flash deklariert keinen neuen Typ, daher kann man dadurch kein anderes
Template aufrufen.
Tja das ist schade, aber wohl nicht zu ändern.
Danke für den Hinweis eine eine mögliche Hilfsklasse wie in WString.h. Dann würde ich aber bei den Variablen im Flash einen entsprechende cast oder eine temporäre Instanz der Hilfsklasse benötigen.
Das ist mehr Aufwand, als zwei Funktionen, eine für RAM Und eine für Flash zu benutzen.
Hab mich mal daran versucht so eine Art statischen Wrapper für die verschiedenen AVR-Adress-Spaces zu basteln. Sodass man Funktionen anhand des Pointer-Typs überladen kann. Es ist alles statisch, also keine "generischen Pointer" die zur Laufzeit weitergegeben werden - es wird nur der Typ mittels templates annotiert.
Ist leider doch etwas komplizierter geworden als erhofft. Man kann dann so schreiben:
1
SPACES_LINEAR(char[3],ramArr2,1,2,3);
2
SPACES_LINEAR(constchar[3],ramArr3,1,2,3);
3
SPACES_PROGMEM_NEAR(char[3],progmemArr2,1,2,3);
4
SPACES_PROGMEM_FAR(char[4],progmemArr3,1,2,3);
5
6
7
SPACES_PROGMEM_NEAR(int,progmemVar,42);
8
SPACES_PROGMEM_FAR(int,progmemVar2,33);
Also Variablen in die verschiedenen Speicherbereiche legen. Man kann dann "einfach so" zugreifen:
1
ramArr2.get(2);
2
ramArr3.get(2);
3
progmemArr2.get(2);
4
progmemArr3.get(2);
5
6
progmemVar.get();
7
progmemVar2.get();
Wobei automatisch die richtige Zugriffsmethode gewählt wird. Dann kann man beliebige Funktionen überladen, das habe ich exemplarisch für strlen und memcmp gemacht. Die kann man dann so aufrufen:
Für das memcmp muss man noch das etwas hässliche ref_static_cast reinschieben um aus dem char-Pointer einen void-Pointer zu machen; bei "nackten" Pointern geht das natürlich automatisch, aber durch das Wrappen im template muss man es manuell machen. Aber man muss eben nicht angeben, welches memcmp es konkret ist, das wird automatisch entschieden.
"Normal" angelegte Variablen (im RAM) kann man via AddrSpaces::linear() ebenfalls annotieren/wrappen, sodass man dann ebenso überladen kann.
Es werden Daten im RAM, PROGMEM, PROGMEM_FAR unterstützt. Man kann aber prinzipiell beliebig Bereiche hinzufügen.
Wenn man dann den Zugriff auf den Flash Speicher "einfach so" abstrahiert hat, wird es damit auch "ganz einfach", ein schlechtes Programm zu schreiben (dass den Flash wie RAM zu behandeln versucht) eben weil man die Komplexität dahinter zu sehr verborgen hat. Dazu kommt, dass die vielen nützlichen Fremd-Bibliotheken dein Konstrukt allesamt nicht nutzen können.
Dazu
kommt, dass die vielen nützlichen Fremd-Bibliotheken dein Konstrukt
allesamt nicht nutzen können.
Zum Anbinden von Fremdbibliotheken kann man immer noch einfach direkt zugreifen, oder einen Wrapper für die Bibliothek schnallen für unterschiedliche Varianten der Funktionen; genau das habe ich ja für strlen vs strlen_P vs strlen_PF gemacht. Das ist ja sowieso genau das, was ursprünglich gefragt war.
Am naheliegendsten wäre ja, __flash etc. in avr-g++ einzubauen.
Ja, allerdings ist es dann ja immer noch kein eigener Typ. Der __flash Qualifier müsste genau wie "const" und "volatile" den Typ ändern, aber das würde das Typsystem derart durcheinander bringen dass man praktisch mit einer neuen Sprache da steht.
Am naheliegendsten wäre ja, __flash etc. in avr-g++ einzubauen.
Ja, allerdings ist es dann ja immer noch kein eigener Typ. Der __flash
Qualifier müsste genau wie "const" und "volatile" den Typ ändern, aber
das würde das Typsystem derart durcheinander bringen dass man praktisch
mit einer neuen Sprache da steht.
Käse, in clang/llvm geht's ja auch, inclusive Mangling etc.
Kann man da auf Basis von __flash überladen? Wie funktioniert dann zB std::remove_cv? Muss man dann für Typfunktionen wie std::remove_pointer neue Overloads hinzufügen und auch dementsprechend sehr viel bestehenden Code anpassen?
Kann man da auf Basis von __flash überladen? Wie funktioniert dann zB
std::remove_cv? Muss man dann für Typfunktionen wie std::remove_pointer
neue Overloads hinzufügen und auch dementsprechend sehr viel bestehenden
Code anpassen?
Mit clang kenn ich mich nicht aus. Builds sind aber im Netz verfügbar.
Und attribute((address_space(x))) ist im Gegensatz zu GCC (wo Attribute nie Qualifier sind) als Qualifier implementiert, steht also auf der selben Ebene wie const und volatile.
Und attribute((address_space(x))) ist im Gegensatz zu GCC (wo Attribute
nie Qualifier sind) als Qualifier implementiert, steht also auf der
selben Ebene wie const und volatile.
Klingt zwar gut aber ich vermute dass dann sehr viel existierender Code wie so ziemlich die ganze C++ Standardbibliothek nicht mehr funktioniert, außer man setzt es nur sehr punktuell ein, das ist dann aber genau so gut wie die GCC Lösung.
Die Pointer-Arithmetik ist da vollständiger umgesetzt als bei mir aber das mit den Overloads und automatischem Aufrufen der richtigen Funktion ist da gar nicht umgesetzt.
Ich würde versuchen mich bei solchen Sachen an der C++ Standard Lib bezüglich der Schnittstelle zu orientieren. Dann kann man später auch viele Standardsachen mit nutzen, siehe Anhang.
Du hast recht, das klappt nicht als nichtstatisches Member.
Wohl aber als statisches. Zudem kann man variadic template parameters direkt zur Initialisierung verwenden (Zeilen 38 und 45). Das Erspart hantieren mit memcmp, Makros etc.
Die zwei ersten template - Parameter sind Datentyp und Speichermodell (linear / progmem / progmem_array). Für progmem_array folgen die im PROGMEM abzulegenden Werte als Liste.
Zudem kann man variadic template parameters direkt zur Initialisierung
verwenden (Zeilen 38 und 45)
Finde ich nicht so elegant, gerade auch bei komplexeren Datenstrukturen. Allerdings funktioniert es ja weil man wohl nicht mehrfach die gleiche Datenstruktur im Flash anlegen möchte. Bei mir kann man es ganz klassisch per Konstruktor übergeben.
Was ist der Unterschied zwischen progmem und progmem_array?
Das memcmp war sicherlich nur ein Beispiel, es gibt ja noch einige mehr Funktionen die für RAM/Flash unterschiedlich sind. Die effizient überladen zu können war das Ziel
Diese Variablen sind ja leer, verbrauchen aber vermutlich trotzdem RAM
(jeweils 1 Byte).
Ich gehe von einem byte Verbrauch aus. Das ist der Preis für die homogene Syntax. Tatsächlich braucht das Beispielprogramm nur 4 Bytes für die linear storages, also für zwei ints. Ich vermute GCC kann das bisschen code noch optimieren, aber bei mehr code wird er dann ein Byte pro progmem belegen.
Auf die Schnelle bekomme ich deinen Code nicht kompiliert. Gibts da einen Trick? Ich habe keine Arduino, sondern nur avr-gcc 13.3.
progmem_array speichert die Parameter 3 ff im PROGMEM. Die könnten mit
dem [] operator geholt werden.
Du könntest auch einen Array-Typ als Parameter an progmem übergeben lassen und dann automatisch operator [] aktivieren oder eben nicht. Dann muss der User das nicht explizit unterscheiden.
Der cast ist überflüssig und zugleich einer zuviel, da addressof eh ein
void*
Nein, es gibt T* oder const T* zurück. Erst const_cast und dann reinterpret_cast wäre lästig wegen der Typangabe, daher einfach erst nach "const void*".
reinterpret_cast ist nicht constexpr. Vielleicht war es in alten C++ -
Versionen?
Richtig, nein war es nie, die alten GCCs haben es nur nicht angemeckert.
Wäre wohl doch besser den "pointer" Typ zum template zu machen und erst beim Zugriff in Progmem zu casten, und für Linear halt nie casten. Mal schauen wann ich dazu komme das einzubauen