Hallo,
ich erarbeite mir das C programmieren im Bereich Microcontroller. Ich habe aktuelle Schwierigkeiten folgenden Code zu verstehen:
1
typedefstruct
2
{
3
__IOuint32_tPMD;
4
__IOuint32_tOFFD;
5
__IOuint32_tDOUT;
6
__IOuint32_tDMASK;
7
__Iuint32_tPIN;
8
__IOuint32_tDBEN;
9
__IOuint32_tIMD;
10
__IOuint32_tIEN;
11
__IOuint32_tISRC;
12
13
}GPIO_T;
14
15
#define PA ((GPIO_T *) PA_BASE)
Wenn ich das richtig verstehe, wird hier ein Makro mit dem Namen PA definiert. Dieses Makro castet die PA_BASE Adresse zu einem Zeiger vom Typ der GPIO_T Struktur.
Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht. Was zeigt hier auf was? Ich verstehe den Zusammenhang zwischen Adresse und Struktur nicht.
Vielen Dank und viele Grüße
Wenn ich das richtig verstehe, wird hier ein Makro mit dem Namen PA
definiert. Dieses Makro castet die PA_BASE Adresse zu einem Zeiger vom
Typ der GPIO_T Struktur.
Stimmt.
Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht.
Ein Zeiger ist eine Adresse, welche angibt wo Daten zu finden sind.
Was
zeigt hier auf was?
Das #define mit dem Macro ist eher fragwürdig. Eine normale Zeigerdefinitione tut't auch und ist transparent. Und Typdefinitionen schreibt man in meistens klein. Reine Großschreibung sollte man nur für MACROS verwenden.
1
GPIO_Tio_daten;// Struct mit dem Namen io_daten vom Typ GPIO_T
2
GPIO_Ttmp;// noch ein Struct vom Typ GPIO_T
3
GPIO_T*pointer;// Zeiger auf GPIO_T
4
pointer=&io_daten;// zeigt jetzt auf io_daten
5
6
tmp=*pointer;// io_daten wird mittels Zeiger in tmp kopiert
Ich verstehe den Zusammenhang zwischen Adresse und
Struktur nicht.
Ist genau der gleiche wie bei einfachen Variablen.
Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht.
Der Zeiger ist die Adresse im Speicher wo diese Struktur beginnt. Also eine Zahl.
Der Typ des Zeigers sagt dem Compiler, auf was für eine Datenstruktur er zeigt. Diese Info mutzt der Compiler, um passende Offsets zum Zeiger zu addieren, wenn du auf die einzelnen Elemente der Struktur zugreifst.
Davon weiß die CPU allerdings nichts, für sie ist das einfach nur eine Adresse/Integer-Zahl.
Nicht gezeigt ist, dass irgendwo im Code PA_BASE definiert ist. Wahrscheinlich in einer Include-Datei.
PA_BASE kann ganz einfach definiert sein, einfach eine Integer-Zahl. Es kann aber auch sein, dass PA_BASE mit einer Reihe verschachtelter Macros definiert ist. Egal wie, PA_BASE hat letztendlich eine Wert, ist eine Zahl.
Die Zahl wird auf (GPIO_T *) gecastet, was für "Pointer auf eine Datenstruktur vom, Typ GPIO_T" steht. Das heißt, dem Compiler wird gesagt er soll die Zahl in PA_BASE als Pointer behandeln.
Nun ist das so, dass hier ausgenutzt wird, dass ein Pointer von vielen Compilern direkt als Speicheradresse verwendet wird. Die nehmen den Wert eines Pointers und benutzen ihn unverändert als eine Adresse im Speicher. Konzeptionell ist das nicht ganz sauber, aber der Trick "Pointer == Speicheradresse" ist absolut üblich wenn man embedded programmiert. Die Hardware hat nun mal ihre Register an festen Speicherpositionen liegen (memory-mapped). Diese Speicherpositionen muss man dem Compiler irgendwie verklickern. Welche das genau sind steht im Handbuch oder Datenblatt des µC.
GPIO_T gibt dann nur noch wieder, wie der Speicher ab PA_BASE aufgebaut ist. Also wo der Compiler Register wie PMD oder OFFD findet. Auch das ist ein etwas unsauberer aber absolut üblicher Trick bei der Embedded-Programmierung. Man muss nur zusehen, dass kein zusätzliche Alignment-Bytes in die Struktur geschoben werden, sonnst stimmt der Zugriff nicht mehr.
Durch die Struktur die ab PA_BASE im Süeicher liegt weiß der Compiler dann, dass PMD auf Adresse PA_BASE + 0, OFFD auf PA_BASE + 32, DOUT auf PA_BASE + 64 liegt, usw.
Danke für die Ganzen Antworten das hat mir sehr weitergeholfen. Hannes deine Erklärung hat mir am besten weitergeholfen.
Vielen Dank
(Und ja PA_BASE hat die Adresse 0x4000 0000)
Bleibt nur noch kurz zu sagen, dass unter den "Speicheradressen" im Zielsystem nicht nur normaler Speicher zu finden ist, sondern auch etwas, das sich special function register nennt. Werte, die man dorthin schreibt, bestimmen, wie sich die Peripherie verhält. Werte, die man liest, verraten den Zustand der Peripherie. Dadurch kann es durchaus sein, dass man nicht den Wert liest, den man kurz vorher noch geschrieben hat. Schreibt man auf das Datenregister der seriellen Schnittstelle, löst man z.B. auf manchen Systemen das Senden dieses Wertes über die Schnittstelle aus. Wenn man jedoch von der Adresse liest, bekommt man den zuletzt empfangenen Wert.
Dein Codeschnipsel erinnert mich an das CMSIS. Das ist Code, den die Firma Arm zur Verfügung stellt, um auf Mikrocontrollern, die auf Technik von Arm basieren, die Peripherieeinheiten zu bedienen.
Bleibt nur noch kurz zu sagen, dass unter den "Speicheradressen" im
Zielsystem nicht nur normaler Speicher zu finden ist, sondern auch
etwas, das sich special function register nennt. Werte, die man dorthin
schreibt, bestimmen, wie sich die Peripherie verhält. Werte, die man
liest, verraten den Zustand der Peripherie. Dadurch kann es durchaus
sein, dass man nicht den Wert liest, den man kurz vorher noch
geschrieben hat. Schreibt man auf das Datenregister der seriellen
Schnittstelle, löst man z.B. auf manchen Systemen das Senden dieses
Wertes über die Schnittstelle aus. Wenn man jedoch von der Adresse
liest, bekommt man den zuletzt empfangenen Wert.
Danke.
Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten "__", was für die Compiler Programmierer vorbehalten bleiben soll. Soviel wusste ich noch.
Der Typ des Zeigers sagt dem Compiler, auf was für eine Datenstruktur er
zeigt. Diese Info mutzt der Compiler, um passende Offsets zum Zeiger zu
addieren, wenn du auf die einzelnen Elemente der Struktur zugreifst.
Davon weiß die CPU allerdings nichts, für sie ist das einfach nur eine
Adresse/Integer-Zahl.
Das muss nicht sein. Hängt von den Adressierungsarten ab, die die Zielmaschine bereitstellt. Es kann durchaus sein (bei Verfügbarkeit der Adressierungsart "Indirekt mit Offset"), dass sich der Compiler überhaupt nicht darum kümmern muss, irgendwelche Offsets zu irgendwelchen Adressen zu addieren. Das macht dann die Maschine selber.
Jedenfalls, wenn der Compiler klug genug ist, diese Adresseierungsart bei Verfügbarkeit auch zu benutzen.
Naja, andererseits: oft lohnt das aber wiederum nur, wenn nacheinander auf mehrere Felder der Struktur zugegriffen wird, ansonsten ist es oft wiederum effizienter, "von Hand" zu addieren und "Direkt" oder "Indirekt" zuzugreifen. Das gilt vor allem dann, wenn sich die Sache schon zur Compilezeit auflösen läßt.
Als C-ler muss man halt einfach nur hoffen, dass der Compiler es je nach Situation schon bestmöglich umsetzen wird. Viele glauben sogar regelrecht und mit voller Inbrunst, dass er das immer und unter allen Umständen tut. Und die ganz Harten machen sogar ein Axiom daraus und behaupten, dass es wirklich so wäre...
Als C-ler muss man halt einfach nur hoffen, dass der Compiler es je nach
Situation schon bestmöglich umsetzen wird. Viele glauben sogar
regelrecht und mit voller Inbrunst, dass er das immer und unter allen
Umständen tut.
Immer sicher nicht, da es auch schlechte Compiler bzw. Optimierer gibt. Es ist aber sehr wahrscheinlich, dass etablierte Compiler auf's Byte bzw. auf den Taktzyklus genau ausrechnen, welche Adressierungsart beim gegebenen Code die bessere ist.
Und das jedes Mal: Nicht nur beim ersten Entwurf, sondern jedes Mal wenn eine Zeile hinzukommt oder wegfällt. OB sich jetzt nicht der komplett andere Ansatz lohnt, weil z.B. jetzt mit ++ statt +3 gearbeitet werden kann.
Das #define mit dem Macro ist eher fragwürdig. Eine normale
Zeigerdefinitione tut't auch und ist transparent.
Nö, nicht, wenn du sowas als Hersteller in einer include-Datei liefern willst. Wenn diese innerhalb eines Exectuables in mehreren Übersetzungseinheiten inkludiert wird, dann würde der Linker die Definition mehrfach sehen. Je nach Linker (und Optionen) beschwert er sich dann drüber.
Ist doch völlig normal, du hast dann halt Zugriffe a la:
1
PA->DOUT=42;
um Daten auf Port A auszugeben (wäre jetz meine Vermutung anhand der Feldnamen in der struct).
Bei den Xmega-artigen AVRs läuft das übrigens genauso, da hast du dann sowas wie
1
#define PORTA (*(PORT_t *) 0x0400) /* I/O Ports */
Danke.
Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten
"__", was für die Compiler Programmierer vorbehalten bleiben soll.
Soviel wusste ich noch.
Nicht für den Compiler sondern für den Linker sind die __ interessant.
Aber bei Macros ist das egal, weil weder Compiler noch Linker die zu sehen bekommen. Der Präprozessor läuft vorher und ersetzt alle Macro durch die Definitionen.
Viele glauben sogar regelrecht und mit voller Inbrunst, dass er
das immer und unter allen Umständen tut.
Und andere glauben ganz doll fest und mit religiöser Inbrunst daran, daß das nie der Fall sein kann und ihr handgeklöppelter Assemblercode schon aus Prinzip allem anderen überlegen sein muss - und treten das hier bei jeder Gelegenheit breit. Was übrigens nichts mit "C-ler" (was für ein beknacktes Kindergartenwort!) zu tun hat, sondern mit dem Dir ja bestens bekannten Unterschied zwischen Assembler und jeder beliebigen Hochsprache, egal, ob die nun C, Pascal, Rust, Ada oder nächste Woche auch GelbrosaRüsselschwein heißt.
Jedenfalls, wenn der Compiler klug genug ist, diese Adresseierungsart
bei Verfügbarkeit auch zu benutzen.
Viele moderne Compiler sind stinkend faul. Ehe sie umständlich Code für das Target erzeugen müssen, der alles erst zur Laufzeit ausrechnet, versuchen sie erstmal alle konstanten Ausdrücke soweit zu vereinfachen, wie zur Compilezeit nur irgendwie möglich.
Sogar Funktionsaufrufe, z.B. aus der math.h werden schon zur Compilezeit aufgelöst.
Danke.
Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten
"__", was für die Compiler Programmierer vorbehalten bleiben soll.
Soviel wusste ich noch.
Nicht für den Compiler sondern für den Linker sind die __ interessant.
Aber bei Macros ist das egal, weil weder Compiler noch Linker die zu
sehen bekommen.
Nö und nö.
Alle Bezeichner, die mit zwei Unterstrichen oder einem Unterstrich und einem Großbuchstaben beginnen, sind in C für die Implementierung (also Compiler und Standardbibliothek) reserviert. Anwendercode darf derartige Bezeichner nicht selbst erfinden und sie nur entsprechend irgendeiner Dokumentation der Implementierung verwenden. Beispiel: "_Bool" gibt es seit C99 auch bereits, ohne dass man stdbool.h inkludiert hat.
In diesem Falle betrachtet sich die CMSIS, die diese Makros definiert, natürlich als Teil der Implementierung.
In diesem Falle betrachtet sich die CMSIS, die diese Makros definiert,
natürlich als Teil der Implementierung.
Entweder das, oder den Autoren ist diese Regel einfach egal. Weil welche Gründe könnten sie haben das "__" voranzustellen?
Kollisionen mit dem Nutzercode vermeiden, weil dieser ja keine Bezeichner dieser Art definieren darf. Aber warum hat die CMSIS dann auch Bezeichner wie "SCB", die kurz und kollisionsanfällig sind aber keine Unterstriche haben? Was wenn z.B. die GCC-Autoren "__I" als Built-In definieren?
Einfach nur "I" ist zu kurz, "INP" oder "I__" fanden sie doof, weshalb man einfach "__I" gewählt hat
Entweder das, oder den Autoren ist diese Regel einfach egal. Weil welche
Gründe könnten sie haben das "__" voranzustellen?
Sie betrachten sich genauso als "system library", wie AVR-LibC das beispielsweise für den AVR-Bereich macht.
Kollisionen mit dem Nutzercode vermeiden, weil dieser ja keine
Bezeichner dieser Art definieren darf.
Genau das.
Aber warum hat die CMSIS dann
auch Bezeichner wie "SCB", die kurz und kollisionsanfällig sind aber
keine Unterstriche haben?
Die bekommst du aber nur, wenn du das entsprechende Headerfile inkludierst.
Diese __I und __IO bekommst du ja "hinten herum", d.h. die Headers, die das definieren, kannst du dir nicht selbst auswählen. An der Stelle sollte man halt schon als Implementierer auf Kollisionsfreiheit achten.
Ähnliches macht AVR-LibC auch: PORTA usw. bekommst du explizit via avr/io.h, aber die darin verwendeten _SFR_IO8 etc. bekommst du "hintenrum", daher sind sie im implementation namespace angelegt.
Was wenn z.B. die GCC-Autoren "__I" als
Built-In definieren?
Die Ersteller einer solchen Bibliothek müssen sich im Zweifelsfalls schon mit den Erstellern des Compilers in irgendeiner Form absprechen, beide sind "the implementation" aus Sicht des C-Standards. Teilweise erfolgen solche "Absprachen" halt auch durch Personalunion. :) Beispiele dafür kannst du bei Georg-Johann Lays Arbeit an AVR-GCC und AVR-LibC finden.
Ja nö, das "__I" und das "SCB" sind beide in der cortex_cm*.h definiert. Das eine kann kollidieren und das andere nicht. Man kriegt entweder beides oder keins.
Also woher kann der Compiler wissen, welche "sin" der
Linker sich aussuchen würde?
Spielt keine Rolle - die Funktionalität von "sin" ist im C-Standard vordefiniert, daher kann der Compiler selbst berechnen was rauskommt, ggf. sogar mit höherer Genauigkeit.
Die __IO usw. werden nur intern in den Headern verwendet.
d.h. Funktionen wie __WFI() oder __enable_irq() soll man gar nicht nutzen?
Man kann also z.B. im SCB->SCR das SLEEPDEEP Bit aktivieren für den Deep-Sleep-Modus, kann diesen dann aber nicht betreten weil __WFI() intern ist und eigentlich nicht aufgerufen werden soll?
Viele glauben sogar regelrecht und mit voller Inbrunst, dass er
das immer und unter allen Umständen tut.
Und andere glauben ganz doll fest und mit religiöser Inbrunst daran, daß
das nie der Fall sein kann
Sowas ist mir noch nie aufgefallen. Nicht mal Moby hat sich jemals zu einer derartigen Äußerung verleiten lassen, zumindest so weit ich weiß.
Die Fetisch-Behauptung "der Compiler wird's optimal richten" ist hingegegen tausende Male zu finden. Und natürlich (zumindest in dieser Allgemeinheit) in jedem Fall FALSCH.
Ich jedenfalls kenne keinen, der das wirklich schafft. Die besten für x86 und x86(64) sind immerhin wirklich recht nah dran, auch die für die "großen" ARMs. Aber der ganze Rest, also die überwältigende Mehrzahl(!) ist noch lange nicht so weit.
Und bei der überwältigenden Mehrheit dieses Rests wiederum ist auch nicht mehr damit zu rechnen, dass sie noch irgendwie besser werden, das kommt dann noch dazu.
Ich jedenfalls kenne keinen, der das wirklich schafft.
Das hatte Moby damals auch behauptet – bis Yalu ihm gezeigt hat, dass
der Compiler selbst für seinen Mini-Code besser war. ;-)
Das ist eine typische Vollidioten-Antwort. Das könntest du sehr wahrscheinlich deutlich besser und differenzierter...
Erstens hatte auch damals Moby keinesfalls behauptet, dass kein Compiler unter keinen Umständen optimalen Code erzeugen könnte (welchselbige Aussage Harald K. völlig idiotisch den Assemblerprogrammierern allgemein unterstellt hat und ich mit Bezug auf Moby nur darauf hinweisen wollte, dass nicht mal der sich jemals zu dieser Aussage verstiegen hat).
Ganz im Gegensatz zu tausenden von Beispiele für die Aussage, das der Compiler schon alles optimal macht. Dieser ganze Ballast von FAKE-Aussagen existiert wirklich. In vielen (viel zu vielen) Fällen unwidersprochen.
Zweitens handelte es sich damals um wirklich trivialsten Code. Ja, bei zunehmender Trivialität schaffen die Compiler häufiger (aber auch nicht immer!) die optimale Lösung. Das allerdings ist bei den Assemblerprogrammierern irgendwie ganz genau so ;O)
Der Unterscheid ist: im Gegenatz zum Compiler können sie viel besser erkennen, wo Optimierung sinnvoll wäre. Denn sie kennen im Gegensatz zum Compiler das Laufzeit-Verhalten ihres Codes ohne dass er laufen muß.
Naja, es gibt für Compiler-generiertes Krams mittlerweile diverse Tools zur Analyse des Laufzeitverhaltens und darauf aufbauender Optimierung. Ist aber nach meiner Erfahrung eher wenig nutzbringend. Bringt meistens nur bei großen Maschinen was, wenn Caches zu optimieren sind. Sonst nix.
Drittens hatte (und hat vermutlich bis heute) Moby nicht wirklich Ahnung von Assemblerprogrammierung. Es war nur das einzige, was er wenigstens so halbwegs konnte und auch das nur für AVR8...
Ganz im Gegensatz zu tausenden von Beispiele für die Aussage, das der
Compiler schon alles optimal macht.
Die darfst du gern raussuchen. Ich kenne eigentlich niemanden, der sowas behauptet – wofür auch?
Aber ist auch egal, hat mit dem Thema eh nichts mehr zu tun. Dass man für Peripherieblöcke derartige Strukturen wählt, hat weniger was damit zu tun, ob ein Compiler damit nett umgehen kann, sondern vielmehr damit, dass sich das auch vom Hardwaredesign anbietet: man kann einen IP-Block mehrfach instanziieren, und die einzelnen Instanzen bekommen jeweils eine neue Basisadresse. Innerhalb des Blocks sind dann alle Teile identisch, damit natürlich auch die Offsets der einzelnen Register. Ob der Compiler nun dafür dann die Basisadresse in einem Register hält und die Offsets im Befehlscode, oder ob er (wenn es zur Compilezeit konstant ist) gleich die Adressen als Direktoperanden nutzt, um solch Kleinkram kann sich dann das maschinenabhängige Backend des Compilers kümmern. Gerade bei RISC sind komplette Adressen als Direktoperanden oftmals nicht so gut.
Viele glauben sogar regelrecht und mit voller Inbrunst, dass er
das immer und unter allen Umständen tut.
Und andere glauben ganz doll fest und mit religiöser Inbrunst daran, daß
das nie der Fall sein kann
"Und andere" heißt nicht, daß das /alle anderen/ sind. Wie wärs, ein bisschen die Schulbank drücken, Sprachverständnis wenigstens auf das Niveau von Sek I bringen?
Wir beschäftigen uns hier unter anderem auch mit Logik, da sollte doch das Erkennen und Verstehen einfacher Aussagen nicht zu sehr überfordern, oder?
Hätte ich "Die anderen" geschrieben, dann sähe die Sache anders aus, aber das habe ich eben nicht.
Viele glauben sogar regelrecht und mit voller Inbrunst, dass er
das immer und unter allen Umständen tut.
Und andere glauben ganz doll fest und mit religiöser Inbrunst daran, daß
das nie der Fall sein kann
"Und andere" heißt nicht, daß das /alle anderen/ sind.
Das genau ist der Knackpunkt. Nichtmal vom mir bekanntesten Negativ-Beispiel eines "Assembler-Programmmierers" konnte ich eine derartige Äußerung finden.
Du aber tust so, als wäre das die übliche Attitüde von Assembler-Programmierern, ohne das auch nur in einem einzigen winzigen Beispiel (und sei es auch nur anhand dieses Assembler-Programmierer-Fakes) nachzuweisen.
Und das IST so, weil das halt wirklich niemand von denen schreiben würde. Offensichtlich nicht mal die Fakes...
Die schwachsinnige Glorifizierung der Fähigkeiten von Compilern ist hingegen scheinbar anerkannte Normalität. Dagegen hast du nichts einzuwenden?
Das kann dann nur durch fehlende Kompetenz bedingt sein.
Ganz im Gegensatz zu tausenden von Beispiele für die Aussage, das der
Compiler schon alles optimal macht.
Die darfst du gern raussuchen. Ich kenne eigentlich niemanden, der sowas
behauptet – wofür auch?
Liest du das (u.a.) von dir verwaltete Forum nicht? Ist irgendwie schwer vorstellbar...
Und du als Moderator hast vermutlich noch wesentlich effizientere Mittel zu Suche zur Verfügung als die öffentlich verfügbare Suchfunktion.
Wenn du bockst, kann ich das übrigens mit mindestens 413 konkreten Postings beweisen, in denen genau das behauptet wird.
Pikant: diese Postings haben oft Erwiderungen provoziert, in denen auffällig über die Norm zensiert wurde. Kannst du auch diese statistisch überaus signafikante Anomalie erklären?
Sehe ich nicht!
Sinnlose Grabenkriege haben in Foren nix zu suchen, würde ich mal sagen.
Von daher halte ich das für eine gelungene Zensur.
Asm vs. Hochsprache:
Wo die Vorteile/Nachteile der jeweiligen Fraktion zu suchen sind, ist doch schon ca. ein 1/2 Jahrhundert klar.
Alle Argumente sind lange schon ausgetauscht.
Keine Neuigkeiten zu erwarten.
Sehe ich nicht!
Sinnlose Grabenkriege haben in Foren nix zu suchen, würde ich mal sagen.
Von daher halte ich das für eine gelungene Zensur.
Asm vs. Hochsprache:
Wo die Vorteile/Nachteile der jeweiligen Fraktion zu suchen sind, ist
doch schon ca. ein 1/2 Jahrhundert klar.
Alle Argumente sind lange schon ausgetauscht.
Keine Neuigkeiten zu erwarten.
Und deswegen dürfen Lügen ungestraft und unwidersprochen blieben (Widersprüche sogar zensiert) werden?
Jo mei, das scheint mir doch eine deutlich parteiische Einstellung zu sein. Ist halt nur dieselbe, die auch der Zensor hat...
Wie auch immer: sie ändert nichts an der objektiven Wahrheit: