Hallo! Kann mir jemand sagen, wie ich Mengen in C syntaktisch richtig schreibe? In Pascal würde ich schreiben "if (a in ['a','b','c']) ...". Gibt es etwas ähnliches auch in ANSI C? Habe im Netz leider keine Beispiele gefunden. Gruß Busi
Nein, so etwas ist in C kein Sprachbestandteil. So etwas muss "von Hand" nachprogrammiert werden. Wenn der Mengenbestandteil ein integer-artiger Typ ist (char, short, long etc.), dann lässt sich dafür ein switch-case-Konstrukt verwenden, aber elegant ist das nicht wirklich: switch (a) { case 'a': case 'b': case 'c': // das, was geschehen soll, wenn a ['a','b','c'] break; } Eine Alternative wäre die Verwendung einer regex-Library (reguläre Ausdrücke), aber deren Speicherbedarf ist je nach Komplexität des auszuwertenden Ausdrucks nicht zu unterschätzen.
Danke für die Antwort. Das ist ein gutes und wahrscheinlich das einzige Workaround. Ich weiß, daß man ein Mengenverhalten über Präprozessordirektiven erzwingen kann, das war aber eine sehr umständliche Aktion und ich weiß auch leider nicht mehr, wie dies funktioniert hat. Aber mit der Switch-Lösung kann ich gut leben. Vielleicht kannst Du mir noch eine Frage beantworten? Ich habe immer wieder versucht Lookup-Tables mit #define ins Flash vom Mikrocontroller zu programmieren. Leider habe ich das bis heute noch nicht hinbekommen. Strings kann ich problemlos dort ablegen, aber Arrays vom Typ unsigned char oder irgendeinen anderen ordinalen Datentyp nicht. Inzwischen weiß ich, daß ich das Schlüsselwort CODE (bei SDCC) verwenden kann, leider funktioniert auch das nicht in jedem Fall wie erwartet. Beispiel: #define abc {1,2,3,4} #define abc[] {1,2,3,4} #define abc[4] {1,2,3,4} #define abc[] (1,2,3,4) Nichts dergleichen funktioniert. #define abc "1234" ... hingegen funktioniert einwandfrei. Ist das ein Syntaxfehler von mir oder einfach nicht möglich? Gruß Busi
Mit #define legst du nichts im Flash ab. Auch dein #define abc "1234" landet nicht im Flash, sondern belegt später RAM. Möglicherweise fehlen dir einfach ein paar C-Grundkenntnisse. Die solltest du bei Gelegenheit auf jeden Fall nachholen, da du sonst immer wieder über solche grundlegenden Verständnisprobleme stolpern wirst. Für dein konkretes Problem, schau dir bitte die avr-libc-Doku an. Da sind schöne Beispiele enthalten, wie man Konstanten und Arrays im Flash ablegt und wieder abruft. Ich sehe es im Moment als unnötig an, das hierher zu kopieren, was dort schon seit Jahren steht.
auh das ist nur bedinngt richtig. eine #define ABC "1234" legt lediglich fest, dass beim compilieren jede freistehende textstelle ABC (wobei (ABC) als freistehend gild, "ABC" jedoch nicht ...)im source (sofern das #define in der quelldatei oder einer "includeten" datei steht) durch "1234" ersetzt wird. PRINTF(ABC); würde dann ABC ausgeben. ob es dann im flash oder im ram steht hängt vom programmcode ab. #define ABC "1234" foo(){ char* szString = ABC; printf( szString ); .... } erzeugt einen variable szString dessen "wert" 1234 bei aufruf von foo() im ram abgelegt wird. das printf(...) gibt dann 1234 aus. #define ABC "1234" foo(){ printf( ABC ); .... } hier gibt printf(...) auch 1234 aus, allerdings steht dieser "wert" nicht aus dem ram sondern ist ein teil des codes und steht im flash. ...
sorry, >... >PRINTF(ABC); >würde dann > >ABC > >ausgeben. > ist natürlich falsch, richtig ist >PRINTF(ABC); >würde dann > >1234 > >ausgeben.
Hi! Angaben wie #define ABC "abc" müssen im Flash landen, alleine schon deshalb, weil sie im MAP-File nicht auftauchen und ich so viele Strings im Programm habe, daß das RAM dafür nicht ausreichen würde. (ok, vielleicht das XRAM). Im LST-File stehen alle Strings am Ende des Speicherbereiches für das Programm, (bei meinem Programm 974 Bytes bis Adresse 0x1362). Im Assembler-Code steht als Area CSEG. Also Flash-Speicher. Und so viele Bytes deshalb, weil ich ganze Bildschirmseiten auf Display und RS232 ausgebe. Mag sein, das zur Laufzeit die Strings einzeln in das RAM geladen werden, wichtig ist ja nur, daß nicht alle gleichzeitig geladen werden. Aber auch das bedeutet ja, daß die Adresse im Flash diejenige ist, von der zur Laufzeit geladen wird. Mit #define mache ich ja nichts weiter, als eine EQUATION zu bauen, (EQU in Assembler). Auf die Strings zugreifen tue ich über einen Pointer. Ergo: Der Stringname ist ein Pointer auf die Adresse, an der der String steht. Gruß Busi
Schau doch mal diesen Link an: http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Vereinfachung_f.FCr_Zeichenketten_.28Strings.29_im_Flash Mit #define kommst du auf jeden Fall nicht weiter, da das nur eine direktive für den Präprozessor ist. Das heißt: #define name "zaphod" printf("%s", name); würde nach dem Präprozessorlauf zu: printf("%s", "zaphod"); Also nur eine simple Ersetzung, der #define selbst verschwindet komplett. Somit kanns auch keinen Pointer auf diesen String selbst geben. Mit char firstname[] = name; (wird dann zu char firstname[] = "zaphod";) hättest du nen Pointer auf den String, der jetzt allerdings im RAM ist. Ich hoffe das macht es etwas klarer. Tom
@spy: Das ist mir schon klar. Natürlich landet prinzipiell erstmal alles (vom EEPROM abgesehen) im Flash. Das war aber nicht, was ich meinte. Das hier: printf(ABC); wird durch den Präprozessor ja umgewandelt in: printf("1234"); Der String "1234" landet natürlich erstmal im Flash. Das Problem ist aber, dass er im Endeffekt wieder in den RAM geladen wird, da er nicht das "PROGMEM"-Attribute besitzt. Bei so einem kleinen String spielt das wohl keine Rolle, bei größeren kann das ins Gewicht fallen. > Ergo: Der Stringname ist ein Pointer auf die Adresse, an der > der String steht. Ja, aber dein Datenpointer, der daher ins RAM und nicht ins Flash zeigt.
Die Aussage von Chris trifft -glücklicherweise- nur für Prozessoren mit Harvard-Architektur zu; bei anderen Prozessoren sieht das durchaus anders aus, die brauchen auch kein PROGMEM-Attribut. Schließlich gibt es ja auch andere µCs als ausgerechnet AVRs ... und niemand hat in diesem Thread explizit von AVRs geredet.
> Die Aussage von Chris trifft -glücklicherweise- nur für Prozessoren > mit Harvard-Architektur zu; bei anderen Prozessoren sieht das > durchaus anders aus, die brauchen auch kein PROGMEM-Attribut. Ich bin mir nicht ganz sicher, aber theoretisch bräuchte man bei AVRs auch kein Progmem-Attribut. C/C++ unterscheidet schließlich zwischen Compile-Time-Konstanten und normalen "Konstanten". Man könnte dem gcc nun bestimmt beibringen, Compile-Time-Konstanten grundsätzlich um Flash abzulegen und über "LPM" etc. darauf zuzugreifen. Dann bräuchte man keine zusätzlichen Funktionen zum Lesen mehr und auch kein PROGMEM. Aber ich meine, das wurde nicht eingeführt, weil ein (const-)Zeiger dadurch signifikant größer und der Zugriff langsamer werden würde. Möglich wäre es aber bestimmt, die Flash-Lese-Funktionen einzusparen, indem man dem Compiler beibringt, dass PROGMEM-Zeiger mit LPM dereferenziert werden müssen. Stand (AFAIK und IIRC) auch schon mal auf der avrlibc-Mailingliste oder Webseite.
"Man könnte dem gcc nun bestimmt beibringen, Compile-Time-Konstanten grundsätzlich um Flash abzulegen und über "LPM" etc. darauf zuzugreifen." Hmm. Bereits das folgende (banale) Beispiel dürfte zeigen, wie ausweglos das Unterfangen ist: char* p = "bla"; char buffer[10]; puts(p); p = buffer; puts(p); Beim ersten Aufruf von puts ist p ein ROM-Pointer. Durch die völlig legale Zuweisung transmogrifiziert p zu einem RAM-Pointer, der dem zweiten Aufruf von puts übergeben wird. Daher muss prinzipiell jeder Pointer die Information RAM/ROM-Pointer mit sich herumschleppen. Jeder Pointerzugriff, ob Zuweisung oder Dereferenzierung, müsste diese Zusatzinformation auswerten - eine Unterscheidung in "Compile-Time-Konstanten" und "normale Konstanten" genügt nicht. Auch die zweite vorgeschlagene Variante (PROGMEM-Zeiger anders dereferenzieren) ist zwiespältig, weil bereits das kurze "Beispiel" ein grundlegendes Problem zeigt: Entweder verhalten sich Pointer so, wie sie das in C nun mal tun, oder aber man entwickelt spezifische Nichtportierbarkeiten, wie die Unterscheidung in zwei Pointertypen, die je nach Kontext unterschiedlich zu gebrauchen sind. Selbst das hülfe nicht; was soll der Compiler beispielsweise bei Funktionen mit variabler Argumentenliste (Beispiel: printf()) für Code generieren? Es muss zur Laufzeit zwischen den verschiedenen Pointertypen unterschieden werden, sonst entfernt sich das ganze zunehmend von C. Etwas provokant ausgedrückt: AVRs sind ganz wundervolle Microcontroller, C ist eine ganz wundervolle Programmiersprache, aber zusammenpassen tun sie nicht.
Das ist nicht provokant sondern wahr. Obwohl ich in C/C++ 'zu Hause' bin habe ich aufgegeben einen ATMega in C zu programmieren.
> Hmm. Bereits das folgende (banale) Beispiel dürfte zeigen, wie > ausweglos das Unterfangen ist: > char* p = "bla"; Hm, da sieht man mal wieder wie "C++-geprägt" ich bin. ;-) In C++ wäre diese Anweisung nicht gut, zumindest deprecated (afaik). p ist nämlich ein Zeiger auf einen nicht-konstanten Wert, "bla" aber eine String-Konstante. Ich meinte diesen Fall (wie man es in C++ sowieso schreiben würde): const char* p = "bla"; Ich weiß gerade nicht so genau, wie es in C mit "const" aussieht. Ich meinte aber auf jeden Fall Zeiger auf konstante Werte mit "Compile-Time-Konstanten"; wenn man "char* p" durchgehen lässt, funktioniert es logischerweise nicht mehr. Aber du hast Recht, printf etc. wären immer noch ein Problem, da ein Parameter keine Compile-Time-Konstante ist. Sieht wohl doch so aus, als wäre die einzige Lösung ein Pointer mit einem zusätzlichen Bit, das angibt ob es ein RAM- oder ein ROM-Pointer ist, was aber wohl vom Speicherverbrauch und der Performance her unakzeptabel wäre. Ich überleg gerade, wie man C früher(tm) benutzt hat, als die Rechner noch mit DOS liefen und zwischen near und far-Zeigern unterschieden werden musste. Ähnliches Problem, selbe Sprache. Ich meine TurboPascal hat das Problem gelöst, indem die Pointer eine Zusatzinfo hatten (bin mir aber nicht sicher). Weiß hier vielleicht jemand, wie das damals in C lief?
> Weiß hier vielleicht jemand, wie das damals in C lief? Auf normalen Rechnern hat man es einfacher. Es läuft eh alles im RAM und initialisierte Daten im Datensegment sind kein Problem, auch damals nicht. Folglich gab es keine Daten im Codesegment und das Problem tauchte nicht auf. Ähnlich macht's ja auch WinAVR. Auch dort sind initialisierte Daten möglich, beim Start kopiert vom ROM uns RAM, nur kann's halt zum Platzproblem werden. Ein Problem ist das nur bei µC-Compilern die "const" Daten auf Harvard-Maschinen ins ROM legen obwohl laut C-Definition "const" und non-"const" Pointer in eine Richtung kompatibel sind. Also muss solch ein Compiler Code erzeugen, der beim Pointer-Zugriff zur Laufzeit die beiden Adressbereiche unterscheidet.
"AVRs sind ganz wundervolle Microcontroller, C ist eine ganz wundervolle Programmiersprache, aber zusammenpassen tun sie nicht." Das stimmt ganz und garnicht ! Man kann auch Harward-Architekturen ganz wunderbar in C programmieren, z.B. der 8051 beweist es: Alle Bibliotheksfunktionen arbeiten mit sogenannten generic Pointern, d.h. die Pointer benötigen 3 Byte, wobei das 3. Byte den Speichertyp klassifiziert. Schreibe ich z.B.: printf( "blabla" ); wird "blabla" im Flash plaziert. Schreibe ich aber: char s1[80] = "blabla"; char * s = s1; printf( s ); ist s ein generic pointer und es funktioniert auch wie gewünscht. Ohne memory specifier wird s1 nun aber im default RAM-Typ des memory models (small: data, large: xdata) abgelegt. Durch: char code s1[80] = "blabla"; kann man aber wieder die Ablage im Flash erzwingen (spart SRAM). Ich sehe überhaupt keinen Grund, warum ein AVR-Compiler sowas nicht können soll. Daß die Handhabung von Flash-Variablen im AVR-GCC etwas umständlich ist, ist also ganz alleine eine Sache des AVR-GCC und nicht der AVR-Architektur. Aber auch bei µCs mit von Neuman Architektur braucht man trotzdem noch extra Specifier, um die Lokalisierung von Variablen wahlweise im Flash , im EEPROM oder im SRAM vornehmen zu können. Nur Architekturen, wo auch der ausführbare Code im RAM liegt, brauchen sowas nicht und nur diese sind also wirklich 100% "C-kompatibel". Peter
. "Alle Bibliotheksfunktionen arbeiten mit sogenannten generic Pointern, d.h. die Pointer benötigen 3 Byte, wobei das 3. Byte den Speichertyp klassifiziert." Das ist aber ziemlich ineffizient. Statt einfach einen Pointer zu dereferenzieren (was bei geeigneten Prozessoren mit zwei Befehlen umgesetzt werden kann - "lade Pointer in Register" - "lade Wert aus Speicher, verwende Adresse im Register dafür"), muss das dritte Byte vor jedem einzelnen Zugriff untersucht werden. "Aber auch bei µCs mit von Neuman Architektur braucht man trotzdem noch extra Specifier, um die Lokalisierung von Variablen wahlweise im Flash, im EEPROM oder im SRAM vornehmen zu können." "storage class specifiers" benötigt man sicherlich, aber die sind ausschließlich bei der Deklaration von Variablen erforderlich. Zugriffe auf Pointer sind bei von-Neumann-Prozessoren vom "storage class specifier" unabhängig, da Speicher nunmal Speicher ist und linear im Adressraum des Prozessors untergebracht ist. Auf üblichen Prozessoren ist somit ein Pointer -egal, mit welchem "storage class specifier" er deklariert ist- ein 16-Bit-Wert, auf den beliebig zugegriffen werden kann. Das Lesen von Werten aus RAM oder ROM unterscheidet sich nicht. Daß das Beschreiben bestimmter Speicherbereiche (Flash oder EEPROM) nicht mit einem einfachen Speicherzugriff zu erledigen ist, dürfte klar sein, aber das muss IMHO ein Compiler auch nicht für den Programmierer erledigen. Damit ist auch klar, das Controllerarchitekturen wie die MCS-51-Reihe mit ihrer Unterscheidung in "normales" RAM und XRAM keine wirklich geeigneter Kandidaten für die Programmierung in C sind.
@Rufus T. Firefly, "Das ist aber ziemlich ineffizient." Die Effizienz bestimmt sich immer aus der Gesamtperformance ! Wenn ich ein Geschoß wie printf() verwende, dann kostet das immer Zeit, das bischen Typunterscheidung ist dagegen voll vernachlässigbar. Schlußendlich bestimmt der Programmablauf, wieviel der gesamten CPU-Last überhaupt solche Pointerzugriffe ausmachen. Global etwas als ineffizient zu bezeichnen ist also falsch ! "muss das dritte Byte vor jedem einzelnen Zugriff untersucht werden." Nicht zwingend ! memcpy() besteht z.B. aus mehreren Routinen, die Auswertung erfolgt also nur beim ersten zu kopierenden Byte. "Damit ist auch klar, das Controllerarchitekturen wie die MCS-51-Reihe mit ihrer Unterscheidung in "normales" RAM und XRAM keine wirklich geeigneter Kandidaten für die Programmierung in C sind." ??? Das ist mir neu ! Was habe ich dann aber all die Jahre (seit etwa 1999) bloß gemacht ? Nachdem ich C einigermaßen konnte, war es eine richtige Erholung gegenüber Assemblerprogrammierung (nie wieder PUSH usw.). Und der Overhead ist fast vernachlässigbar (im Durchschnitt 5..20%). Die SRAM-Nutzung ist sogar geringer, da der Linker den SRAM viel besser verwalten kann, als ein Mensch in Assembler. D.h. ich kriege in C viel größere Projekte im gleichen SRAM unter, als vormals in Assembler ! Peter
Ich meine ja auch nicht, daß man AVR und MCS-51 überhaupt nicht in C programmieren könne, sondern daß aufgrund deren Architekturen etliche Probleme auftreten, die man mit einer geradlinigeren Prozessorarchitektur eben nicht hat. Natürlich kann man -wie Du es auch gemacht hast- den MCS51 in C programmieren, aber man muss eben auch an etliche Spezialitäten denken, an die man bei anderen Prozessoren nicht denken muss. "Global etwas als ineffizient zu bezeichnen ist also falsch !" Ich will mich micht mit Dir über Logik streiten ... Du vergleichst Äpfel mit Gurken - meine Kritik an der (in)Effizienz von Pointerzugriffen auf MCS51/AVR mit dem "Wirkungsgrad" der Programmierung in C auf diesen Controllern. Anstelle eines Pointerzugriffes, der ein oder zwei Maschinenbefehle lang ist (ersteres, wenn der Pointer eh' schon in einem Prozessorregister steht), durch die erforderliche Pointertypüberprüfung weitere Befehle ausführen zu müssen und sich obendrein die Möglichkeit zu nehmen, einen Pointer in einem Prozessorregister zu halten, ist ineffizient. Punkt. Es ist -vom Standpunkt der Effizienz des Gesamtsystemes her gesehen- immer sinnvoll, in einer Hochsprache mit einem brauchbar optimierenden Compiler zu programmieren, ob das nun C, Pascal oder was auch immer ist. Das bestreite ich nicht; ich programmiere nicht ohne Grund seit längerem in C (so etwa seit Ende der 80er). Nur bieten bestimmte Prozessorarchitekturen deutlich bessere Möglichkeiten für die Programmierung in C als andere. Vergleicht man -der Rückblick in die Geschichte sei mir an dieser Stelle gestattet- beispielsweise den Befehlssatz der beiden Prozessoren 6502 und 6809, so ist aufgrund der 16-Bit-Indexregister, die der 6809 hat und der erweiterten Adressierungsarten, die im Zusammenhang mit diesen Indexregistern möglich sind, ein deutlich kompakterer und schnellerer Maschinencode möglich, als mit dem nur wenige Jahre älteren 6502. "Die SRAM-Nutzung ist sogar geringer, da der Linker den SRAM viel besser verwalten kann, als ein Mensch in Assembler." Dafür musst Du aber kein C einsetzen, dafür sollte ein Assembler, der einzelne Objektdateien erzeugt, die von einem Linker gelinkt werden, völlig genügen. Auch das hat rein gar nichts mit der Geradlinigkeit von Prozessorarchitekturen zu tun, ob nun von-Neumann oder Harvard ... Und also bleibt meine Kernaussage, daß sich nämlich die Harvard-Architektur nur schlecht mit der Programmierung in C verträgt, bestehen.
@Rufus T. Firefly "Dafür musst Du aber kein C einsetzen, dafür sollte ein Assembler, der einzelne Objektdateien erzeugt, die von einem Linker gelinkt werden, völlig genügen." nein, dem ist ganz und garnicht so ! Der Linker weiß, daß ein vom Compiler erzeugtes Objekt bestimmten Konventionen folgt und kann sich darauf einschießen. Assemblerprogramme besitzen aber alle Freiheiten und dann muß der Linker viele Optimierungen abschalten. Man kann ja den Compiler auch erstmal eine Assemblersource erzeugen lassen, diese assemblieren und linken. Bloß kommt dabei in der Regel ein Hex-File raus, das größer und langsamer ist und mehr SRAM verbraucht. Deshalb halte ich auch nichts davon, leichtfertig Assembler und C zu mixen, wenn ein reines C auch schon gut genug ist. Und da schließt sich auch schon wieder der Kreis: Effizienz ist immer im Vergleich zu irgendwas. Es ist müßig, sich beim Hausbau um einen Nagel mehr oder weniger zu streiten, genau, wie bei einem Programm um einen Zyklus mehr oder weniger auf 1000 Zyklen bezogen. Dir scheint es ja sehr wichtig zu sein, aber ich wüßte wirklich nicht, warum ich um jeden CPU-Zyklus feilschen sollte und immer nach dem schnellsten jagen muß. Ich sehe einfach überhaupt keinen Sinn darin, ob die CPU nun zu 60% oder zu 99% nur Däumchen dreht. Eine Anwendung, die eine gestellte Aufgabe 1000-mal schneller als gefordert ausführen könnte, ist nicht um einen Fliegenschiß effizienter. Es ist mir also völlig wurscht, ob ein generic pointer einen tick langsamer ist und 0,1% mehr Rechenzeit verbraucht, ich dafür aber 2h bei der Programmierung spare und das Programm weniger potentielle Fehlerquellen enthält. Effizienz in der Softwareentwicklung bedeutet für mich, daß man in möglichst kurzer Zeit eine möglichst stabile und fehlerfreie und wartbare Anwendung fertig stellen kann und noch genügend Reserven für den üblichen Erweiterungsbedarf hat. Ich bin mir sicher, mit einem MSP340-Compiler würde ich weit weniger effizient arbeiten, einfach weil ich mit dem Keil C51 viel mehr Erfahrung habe. Um es abzuschließen, von-Neumann oder Harvard ist mir scheißegal, Hauptsache der Compiler kommt damit zurecht (und ich mit ihm). Peter
"Eine Anwendung, die eine gestellte Aufgabe 1000-mal schneller als gefordert ausführen könnte, ist nicht um einen Fliegenschiß effizienter." Das ist so pauschal nicht ganz richtig, weil die CPU bei der Programmausführung mehr Strom braucht als im idle- oder powerdown-Modus. In bestimmten (zugegeben: weniger häufigen) Anwendungen kann das durchaus wichtig sein.
"Eine Anwendung, die eine gestellte Aufgabe 1000-mal schneller als gefordert ausführen könnte, ist nicht um einen Fliegenschiß effizienter." Das mag sein, das diese 1000fach langsamer Anwendung ihren Job, für aich betrachtet, effizient genug macht. Trotzdem darf man nicht den Gesamtzusammenhang verlieren: Oft laufen auf eine uC mehrer Prozesse gleichzeitig ineinander verschachtelt. Teilweise in einer getriggerten Mailoop, teilweise Interruptgesteuert. Nun nutzt es nix, wenn z.B. eine Temeraturregelung die nur Frequenzen im mHz (=milliHerz) Bereich verabrbeiten muß sich diese Zeit auch nimmt, dadurch aber die anderen Threads wie z.B. Reaktion auf Komandos aus der seriellen Schnittstelle nicht mehr genug Computing-Power kriegen um ihre Aufgabe zu machen. Und nun sind wir an einem Punkt angelangt, wo es wichtig ist zu kennen was mein Compiler so anstellt. Und vorallem welche Seiteneffekte entstehen. Nur dann kann ich abhilfe schaffen. Deshalb meine ich: Über Performanc nachzudenken ist wichtig, wenn's global mit der Abarbeitungszeit im Gesamtsystem eng wird. Nicht eher und auf keinen Fall später. Grüße
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.