Hallo Ich habe ein paar Fragen zu Konstrukten in C bzw. embedded C. Schon öfter habe ich hier im Forum super Antworten gefunden. Jetzt haben sich leider noch ein paar offene Fragen ergeben. 1. Aufzählungsdatentyp enum boolean {FALSE, TRUE}; Die Interpretation dieser Zeile ist mir noch nicht ganz klar. Enum ist das Schlüsselwort für einen Aufzählungsdatentyp und boolean der Name. Ich erfinde hier sozusagen einen neuen Datentyp. Ich stelle mir einen Datentyp ähnlich wie einen mathematischen Körper vor. Man braucht zur Definition eine Zahlenmenge und Operationen. Hier wurde jetzt eine Menge definiert aber keine Operationen. Die Aufzählungskonstanten werden als Integer behandelt. Vielleicht ist das der Grund, dass die Operationen schon indirekt über den Integer Datentyp vorgeben sind. Trifft das zu? Was genau passiert in der Zeile mit dem Speicher? Und wie nennt man diese Zeile? Es könnte eine Definition (Deklaration + Initialisierung) sein, da sowohl Speicher reserviert wird, als auch Startwerte vergeben werden. Anwendungsbeispiel: enum zahl {EINS, ZWEI, DREI, VIER}; enum zahl x; //x ist jetzt vom Typ enum x= ZWEI; //ZWEI steht für einen Integerwert = 1 => x=1. enum zahl {EINS, ZWEI, DREI, VIER}; int x; // x ist vom Typ int x = ZWEI; //x hat wieder den Wert 1 Worin liegt hier der Unterschied? Ich glaube das x in Beispiel 1 kann nur Werte aus den geschweiften Klammern annehmen und das x aus Beispiel 2 kann alle möglichen Werte aus dem Wertebereich des Integers annehmen. Doch in C wird hier doch keine Typüberprüfung stattfinden, sodass es letztendlich keinen Unterschied machen wird. Der Compiler wird beides Mal fehlerlos durchlaufen. Welchen Sinn haben enums, wenn es letztendlich nur Konstanten sind, die automatisch aufsteigend initialisiert werden. Das kann ich auch per Hand machen... 2. Typedef typedef enum zahl {EINS, ZWEI, DREI, VIER} zahl; Ab jetzt kann ich mit zahl x; eine Variable vom Typ enum zahl deklarieren. Wie viel Speicher wird für sie reserviert? Wieso reicht hier nicht typedef enum zahl zahl; ? Alternativ müsste es auch so gehen. enum zahl {EINS, ZWEI, DREI, VIER} zahl; #define (enum zahl) zahl; Das eine ist eine Anweisung für den Präprozessor und das andere für den Compiler. Typedef ist wahrscheinlich etwas ordentlicher. Es ist aber egal wie man es macht oder? 3. Strings in C String sind in C nichts anderes als Char Arrays mit einer abschließenden 0. Wo ist der Unterschied im Speicher zwischen einer abschließenden 0 und einer "echten" 0. Beispiel: "HALL0" => 'H'|'A'|'L'|'L'|'0'|'0' ? Hier könnte bei der Speicherauslesung auch schon eins früher abgebrochen werden, da ist ja eine 0 => Ende des Strings. 4. Strings als Parameter Wenn ich ein Char array an eine Funktion übergebe, dann geht es nur über Zeiger und ist dadruch automatisch Call-by-Reference. Wieso wurde keine Möglichkeit gegegben bei Arrays mit Kopien zu arbeiten? Wenn ich trotzdem mit einer Kopie arbeiten will, müsste ich relativ umständlich mit einer for-Schleife das Array kopieren... Wäre das sauberer Stil oder macht man das irgendwie anders? 5. Modularisierung Wieso erstellt man nicht ein Headerfile was alle gloablen Funktionen und Variablen als extern beinhaltet und dann in jedes C File eingebunden wird. Dann müsste alles genau so funktionieren. Ok, man kann die Module nicht mehr so gut wieder verwenden, aber sonst dürfte eigentlich kein Nachteil entstehen. Ok die Frage ist etwas sinnlos... Ich habe irgendwo gelesen, dass man in einem Headerfile keinen Spiecherplatz anfordern darf, also keine Varibalen deklarieren. Wieso darf man das nicht? Globale Variablen, insbesondere Konstanten wären dort doch gut aufgehoben. Ich erstelle ein Header file globaleKonstanten.h und binde es in jedes C-File ein. Wird dann ganz oft Speicherplatz für die Konstante angelegt oder ist der Compiler schlau genug und macht es nur einmal für alle C Files? Es ist leider etwas länger geworden. Ich hoffe ihr könnt mir bei dem ein oder anderen Problem helfen. Viele Grüße!
:
Bearbeitet durch User
Zweistein schrieb: > 1. Aufzählungsdatentyp > Hier wurde jetzt eine > Menge definiert aber keine Operationen. C ist eine Programmiersprache, keine formale Beschreibung von Mathematik. Die Operationen auf Aufzählungstypen sind von der Sprache her fest vorgegeben. > Was genau passiert in der Zeile mit dem Speicher? Nichts. Es wird ein Typ "enum boolean" definiert. > Und wie nennt man diese Zeile? Typdeklaration. > Worin liegt hier der Unterschied? Der ist gering. Aufzählungstypen sind C eher schwach definiert und wenig restriktiv. > Welchen Sinn haben enums, wenn es letztendlich nur Konstanten sind, die > automatisch aufsteigend initialisiert werden. Das kann ich auch per Hand > machen... Korrekt erkannt. Ist bloss einfacher. Es erspart aber den Präprozessor für Dinge, die eigentlich in den Compiler gehören. > Wieso reicht hier nicht typedef enum zahl zahl; ? Weil enum zahl; eine unvollständige Deklaration ist. Nur enum { ... } ist eine Vollständige Deklaration. Zulässig ist aber typedef enum { ... } zahl; > #define (enum zahl) zahl; Das geht nun wirklich nicht. > Wo ist der Unterschied im Speicher zwischen einer abschließenden 0 und > einer "echten" 0. Keiner. > Wieso wurde keine > Möglichkeit gegegben bei Arrays mit Kopien zu arbeiten? Dennis Ritchie kann man nicht mehr fragen, also ... Hat wohl mit Effizienz zu tun. C ist als Sprache entwickelt worden, die mit möglichst einfachen Mitteln zu Erfolg führt, also komplexe Operationen nicht schon als Sprachelement enthält. Und so sind Arrays eben keine Datentypen, mit denen man direkt umgehen kann. Man kann sie ja auch nicht zuweisen. > sauberer Stil oder macht man das irgendwie anders? Genau so gehts. > Wieso erstellt man nicht ein Headerfile was alle gloablen Funktionen und > Variablen als extern beinhaltet und dann in jedes C File eingebunden > wird. Macht man. Bei grösseren Programmen empfiehlt es sich aber, den einzelnen .c Files individuelle .h Files zuzuordnen, um den Überblick zu behalten. > Ich habe irgendwo gelesen, dass man in einem Headerfile keinen > Spiecherplatz anfordern darf, also keine Varibalen deklarieren. Wieso > darf man das nicht? Wenn ein Headerfile von mehreren .c Files genutzt und getrennt kompiliert wird, dann gibts diese Variablen mehrfach. Der Linker wird das nicht danken. > genug und macht es nur einmal für alle C Files? Eben nicht. Machmal schon, insbesondere bei älteren Systemen kann das vorkommen. Ist aber in C unzulässig.
:
Bearbeitet durch User
A. K. schrieb: >> Wo ist der Unterschied im Speicher zwischen einer abschließenden 0 und >> einer "echten" 0. > > Keiner. Ich vermute, er meint den Unterschied zwischen dem Zeichen '0' und dem abschliessenden Nullzeichen '\0'. Denn folgende Zeile müsste anders aussehen: > "HALL0" => 'H'|'A'|'L'|'L'|'0'|'0' Korrekt: > "HALL0" => 'H'|'A'|'L'|'L'|'0'|'\0'
:
Bearbeitet durch User
Zweistein schrieb: > 3. Strings in C > String sind in C nichts anderes als Char Arrays mit einer abschließenden > 0. > Beispiel: > "HALL0" => 'H'|'A'|'L'|'L'|'0'|'0' ? > Hier könnte bei der Speicherauslesung auch schon eins früher abgebrochen > werden, da ist ja eine 0 => Ende des Strings. Nein. Denn du schreibst da das ASCII-Zeichen '0' in den Speicher, also den wert 48. Das Null-Byte (Stringbegrenzer/Stringendezeiche) '\0' wird aber als Wert 0 (null) in den Speicher geschrieben. Du must unterscheiden zwischen dem String bzw. dem Buchstaben 0 (null) und der Zahl 0 (null) '0' != '\0'
be stucki schrieb: >> Keiner. > > Ich vermute, er meint den Unterschied zwischen dem Zeichen '0' und dem > abschliessenden Nullzeichen '\0'. Ups, ja da hat er '0' und 0 verwechselt.
Zweistein schrieb: > Wenn ich trotzdem mit einer Kopie arbeiten will, müsste ich relativ > umständlich mit einer for-Schleife das Array kopieren... Wäre das > sauberer Stil oder macht man das irgendwie anders? Wenn man Arrays als ganzes übergeben will, kann man sie in eine Struktur packen:
1 | struct MyStruct{ |
2 | int Array[10]; |
3 | };
|
Bei kleinen Arrays kann das je nach Anwendungsfall sinnvoll sein, meistens aber nicht. Bedenke, dass somit das gesamte Array auf den Stack kopiert wird. Schliesslich werden selbst Strukturen meist per Zeiger übergeben. Wenn es genügt, lesend oder schreibend auf das Array zuzugreifen, reicht ein Zeiger vollkommen aus. Sollen die Daten verändert werden, ohne dabei die Originaldaten zu verändern, gibt es zwei Möglichkeiten: Die Daten Wert für Wert einzeln in einer Schleife bearbeiten und so zu einem Resultat zusammenführen oder das Array mit einer Schleife oder memcpy kopieren.
:
Bearbeitet durch User
be stucki schrieb: >> "HALL0" => 'H'|'A'|'L'|'L'|'0'|'0' > > Korrekt: >> "HALL0" => 'H'|'A'|'L'|'L'|'0'|'\0' Fast: 'H'|'A'|'L'|'L'|'0'|'\0' => 125 => '}' "HALL0" => (char[]]){'H','A','L','L','0',0}; Zweistein schrieb: > enum zahl {EINS, ZWEI, DREI, VIER}; Hier ist EINS=0 Besser: enum zahl {EINS=1, ZWEI, DREI, VIER};
Daniel A. schrieb: > 'H'|'A'|'L'|'L'|'0'|'\0' => 125 => '}' Für diejenigen, die jetzt kurz stutzen: Daniel weist darauf hin, daß | ein binäres ODER ist.
Daniel A. schrieb: > be stucki schrieb: >>> "HALL0" => 'H'|'A'|'L'|'L'|'0'|'0' >> >> Korrekt: >>> "HALL0" => 'H'|'A'|'L'|'L'|'0'|'\0' > > Fast: > 'H'|'A'|'L'|'L'|'0'|'\0' => 125 => '}' > "HALL0" => (char[]]){'H','A','L','L','0',0}; Wenn dann gleich ganz korrekt: "HALL0" => (const char[]){'H','A','L','L','0',0}; ;)
Vielen Dank für die vielen tollen Antworten. Es ist schon fast alles geklärt. Ein paar Sachen gibt es leider doch noch. 1. Enum - Verständnis enum zahl {EINS, ZWEI, DREI, VIER} zahl; Diese Zeile ist also eine Typdeklaration und eine Variablendeklaration in einem. Ist das richtig? Die Variable zahl ist dann vom Typ int. Kann ich auch ein enum definieren, bei dem die Aufzählungskonstanten nicht vom Typ int sind? 2. Enum - Beispiel enum zahl {EINS, ZWEI, DREI, VIER}; #define (enum zahl) zahl; Ab jetzt könnte ich jedes Mal eine Variable vom Typ enum zahl so anlegen: zahl x = EINS; oder? Kann x jetzt nur Werte aus enum zahl annehmen, also 0,1,2,3 oder alle die in einen Integer passen? 2. Globale Variablen und Globale Funktionen Ich habe drei Module (3 C-Files und dazu 3 H-Files) und eine main.c. Was ist jetzt besserer Stil, alle gloablen Variablen per extern in ein Header-File zu schreiben und in main.c zu definieren oder definiert man sie in ihr passendes Modul und jeweils ins Header-File extern mit der Premisse jedes Header-File in jedes Modul einbinden zu müssen, weil ja überall die globalen Variablen gebraucht werden können? Das ist ein komischer Satz geworden ;=) Hat ein extern vor einer Funktionsdeklaration in einer Header-Datei eine Bedeutung, eigentlich dürfte ja auch die Funktionsdeklaration ohne extern reichen? Ich sehe keinen Unterschied? Wenn dann durch die Inkludierung von H-Files dieselbe Funktion in mehreren C-Files deklariert wird, ist das ja eigentlich kein Problem, oder? 3. Zeiger - Register ansprechen Ich sehe hier drei Möglichkeiten, bei denen mir die Vor und Nachteile noch nicht ganz klar sind. static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; static volatile uint8_t* const Register = 0x123; Wieso wird beim ersten Fall, die Adresse noch so komisch gecastet? Wenn ich jetzt das Register beschreiben möchte, muss ich das in beiden Fällen so machen *Register = Wert; Das ist irgendwie doof und umständlich mit dem Stern... Dann gibt es noch die Möglichkeit per Define: #define Register (*(volatile int*) (0x123)) Hier wird dann erst die Adresse in ein volatile int Zeiger gecastet und dann dereferenziert. Der Zeiger wird quasi nicht richtig angelegt, er wird quasi nur temporär benutzt, immer genau dann wenn Register = Wert benutzt wird und es so zur Ersetzung durch den Präprozessor kommt. Der Zeiger besetzt also nie permanent Speicherplatz. Irgendwie habe ich das noch nicht richtig verstanden, kann man das auch ausführlicher schreiben? Was ist hier der Vor- und Nachteil? Beste Grüße!
Zweistein schrieb: > Diese Zeile ist also eine Typdeklaration und eine Variablendeklaration > in einem. Ist das richtig? Ja. Zweistein schrieb: > Die Variable zahl ist dann vom Typ int. Kann > ich auch ein enum definieren, bei dem die Aufzählungskonstanten nicht > vom Typ int sind? Laut Standard nicht. Kapitel 6.4.4.3: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf > An identifier declared as an enumeration constant has type int. Bei einigen Compilern gibt es einen Schalter, der für Enumerationen den kleinsten Datentyp wählt, mit dem noch alle Konstanten repräsentiert werden können. Zweistein schrieb: > enum zahl {EINS, ZWEI, DREI, VIER}; > #define (enum zahl) zahl; > > Ab jetzt könnte ich jedes Mal eine Variable vom Typ enum zahl so > anlegen: > zahl x = EINS; oder? Nein. Du müsstest folgendes schreiben:
1 | enum zahl x; |
Wenn du nur zahl schreiben willst, musst du typedef nutzen:
1 | typedef enum {EINS, ZWEI, DREI, VIER} zahl; |
Zweistein schrieb: > Kann x jetzt nur Werte aus enum zahl annehmen, also 0,1,2,3 oder alle > die in einen Integer passen? Alle, die in einen int passen. Zweistein schrieb: > Was ist jetzt besserer Stil, alle gloablen Variablen per extern in ein > Header-File zu schreiben und in main.c zu definieren oder definiert man > sie in ihr passendes Modul und jeweils ins Header-File extern mit der > Premisse jedes Header-File in jedes Modul einbinden zu müssen, weil ja > überall die globalen Variablen gebraucht werden können? In das Modul, in das sie gehören. Wobei zu viel globale Variablen ein Zeichen dafür sein können, das du von C noch nicht alles verstanden hast. Man übergibt Werte an Funktionen und erhält von ihnen ein Resultat in Form eines Rückgabewerts. Ich benötige globale Variablen in der Regel nur im Zusammenhang mit Interrupts. Dateilokale Veriablen kommen aber schon mal vor. Zweistein schrieb: > Hat ein extern vor einer Funktionsdeklaration in einer Header-Datei eine > Bedeutung, eigentlich dürfte ja auch die Funktionsdeklaration ohne > extern reichen? Ich sehe keinen Unterschied? Es bedeutet auch das Gleiche. Weil es nicht nötig ist, schreibt man es meist nicht hin. Zweistein schrieb: > Wenn dann durch die Inkludierung von H-Files dieselbe Funktion in > mehreren C-Files deklariert wird, ist das ja eigentlich kein Problem, > oder? Stichwort: Include Guards Zweistein schrieb: > static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; > static volatile uint8_t* const Register = 0x123; > > Wieso wird beim ersten Fall, die Adresse noch so komisch gecastet? Damit dir, wenn du den Code in ein paar Jahren nochmals bearbeitest, auf den ersten Blick siehst, dass dies so gewollt und kein Fehler ist. Ausserdem wird wohl der Compiler eine Warnung werfen. Makes pointer from integer without a cast. Oder so was Ähnliches. Zweistein schrieb: > Dann gibt es noch die Möglichkeit per Define: > #define Register (*(volatile int*) (0x123)) Überlasse möglichst wenig dem Präprozessor und möglichst viel dem Compiler. Durch solche (in diesem Fall eher noch harmlosen) Konstrukte entstehen früher oder später fast unauffindbare Fehler. Zweistein schrieb: > Der > Zeiger besetzt also nie permanent Speicherplatz. Natürlich belegt das Speicherplatz. Woher soll den dein Controller/Prozessor wissen, wo er was hinschreiben soll? Kurz gesagt: Machs mit dem Stern, so wie jeder andere auch. So sieht man sofort, dass mit Zeigern hantiert wird. Mit der richtigen IDE wird dir auch noch gleich der Datentyp angezeigt, wenn du mit der Maus drüberfährst. Das Makro musst du erstmal suchen. Nochwas: Bitte vergiss das Präprozessorzeugs, nur um dir die Sprache irgendwie zurechtzubiegen.
Zweistein schrieb: > Kann ich auch ein enum definieren, bei dem die Aufzählungskonstanten > nicht vom Typ int sind? Nein. Ist auch nicht nötig. ich verwende Enums oft als Listenindex. > #define (enum zahl) zahl; > Ab jetzt könnte ich jedes Mal eine Variable vom Typ enum zahl so > anlegen: > zahl x = EINS; oder? nein, das müsste dan so aussehen: enum zahl x = EINS; oder typedef enum zahl zahl oder #define zahl enum zahl > Kann x jetzt nur Werte aus enum zahl annehmen, also 0,1,2,3 oder alle > die in einen Integer passen? > Theoretisch alle, Praktisch würde ich es nicht versuchen. > > 2. Globale Variablen und Globale Funktionen In c sind alle Funktionen global Ich würde globalen variablen, welche nur innerhalb einer einzigen Kompilationunit (nur in einer .c datei) als static deklarieren, und in keine Header eintragen. Globale Variablen würde ich in der .h Datei zur .c Datei als extern definieren, und in der .c Datei deklarieren. > Hat ein extern vor einer Funktionsdeklaration in einer Header-Datei eine > Bedeutung Nein, bzw. ist nur bei der definition erlaubt und dort überflüssig. > Wenn dann durch die Inkludierung von H-Files dieselbe Funktion in > mehreren C-Files deklariert wird, ist das ja eigentlich kein Problem, > oder? Das gibt einen linkerfehler in der form "multiple definition of ..." x.h Files sollten mit: #ifndef X_H #define X_H beginnen und mit: #endif enden > 3. Zeiger - Register ansprechen Falsch. Zeiger haben nichts mit Registern zu tun. > Ich sehe hier drei Möglichkeiten, bei denen mir die Vor und Nachteile > noch nicht ganz klar sind. > > static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; > static volatile uint8_t* const Register = 0x123; > > Wieso wird beim ersten Fall, die Adresse noch so komisch gecastet? Vermutlich weil 0x123 ein int ist und kein pointer vom typ volatile uint8_t*. Das volatile weist darauf hin, das sich das Ziel der Adresse auch unabhängig vom Programm verändern kann. > > Wenn ich jetzt das Register beschreiben möchte, muss ich das in beiden > Fällen so machen > > *Register = Wert; Pointer,Zeiger,(Adresse) aber nicht Register > Das ist irgendwie doof und umständlich mit dem Stern... dann nimm memcpy(Register,&Wert,sizeof(Wert)); > Dann gibt es noch die Möglichkeit per Define: > #define Register (*(volatile int*) (0x123)) > > Hier wird dann erst die Adresse in ein volatile int Zeiger gecastet und > dann dereferenziert. Nein. Hier wird nur jedes vorkommen von Register das folgt durch (*(volatile int*) (0x123)) ersetzt. Man arbeitet also mit einer konstante stat mit einer Variablen.
Daniel A. schrieb: >> static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; >> static volatile uint8_t* const Register = 0x123; >> >> Wieso wird beim ersten Fall, die Adresse noch so komisch gecastet? > Vermutlich weil 0x123 ein int ist und kein pointer vom typ volatile > uint8_t*. Das volatile weist darauf hin, das sich das Ziel der Adresse > auch unabhängig vom Programm verändern kann. Das verstehe ich nicht. Bei einem normalen Zeiger caste ich vorher ja auch nicht, wenn ich eine Adresse zuweise. Wieso sollte ich es dann hier tun? Jetzt zu einem embedded nahen Problem. Atmel scheint die define Variante zu nutzen. #define PORTB (*(volatile uint8_t*)(0x123)) Wenn ich jetzt PORTB als Parameter an eine Funktion übergeben will, treten noch Probleme auf. Definition void setPORTB(uint8_t *PORT) { *PORT = Wert; } Aufruf setPORTB(&PORTB); Stimmt das so mit der Funktion?
Daniel A. schrieb: > In c sind alle Funktionen global Innerhalb eines Moduls. Auf Modulebene hingegen nur dann, wenn sie nicht als static deklariert sind.
Zweistein schrieb: > Daniel A. schrieb: >>> static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; >>> static volatile uint8_t* const Register = 0x123; >>> >>> Wieso wird beim ersten Fall, die Adresse noch so komisch gecastet? >> Vermutlich weil 0x123 ein int ist und kein pointer vom typ volatile >> uint8_t*. Das volatile weist darauf hin, das sich das Ziel der Adresse >> auch unabhängig vom Programm verändern kann. > > > Das verstehe ich nicht. Bei einem normalen Zeiger caste ich vorher ja > auch nicht, wenn ich eine Adresse zuweise. Wieso sollte ich es dann hier > tun? Weil 0x123 keine Adresse ist. 0x123 ist ein stink normaler Integer. Nur weil eine Adresse etwas numerisches ist, bedeutet das nicht, dass Zahlen damit automatisch als Adressen fungieren können. > Jetzt zu einem embedded nahen Problem. Atmel scheint die define Variante > zu nutzen. > > #define PORTB (*(volatile uint8_t*)(0x123)) Ja, weil das dem Compiler Optimierungspotential bietet. > Wenn ich jetzt PORTB als Parameter an eine Funktion übergeben will, > treten noch Probleme auf. > > Definition > void setPORTB(uint8_t *PORT) > { > *PORT = Wert; > } > > Aufruf > setPORTB(&PORTB); > > Stimmt das so mit der Funktion? Fast Du hast das volatile vergessen
1 | void setPORTB( volatile uint8_t *Port ) |
2 | {
|
3 | *Port = Wert; |
4 | }
|
Karl Heinz schrieb: > Weil 0x123 keine Adresse ist. > 0x123 ist ein stink normaler Integer. > > Nur weil eine Adresse etwas numerisches ist, bedeutet das nicht, dass > Zahlen damit automatisch als Adressen fungieren können. Ok Aber eine normale Zahl wird dann bei der Zeigerinitialisierung als Adresse interpretiert. static volatile uint8_t* const Register = (volatile uint8_t*) 0x123; static volatile uint8_t* const Register = 0x123; Abschließend kann man sagen, die Zeilen machen das gleiche nur die obere Zeile bietet einen bessere Wiederverwendbarkeit, weil jeder sofort weiß, was gemeint ist. Ist das so jetzt richtig?
Zweistein schrieb: > Ok Aber eine normale Zahl wird dann bei der Zeigerinitialisierung als > Adresse interpretiert. Ja. Aber die meisten Compiler warnen davor. Und das aus gutem Grund. Denn im Eifer des Gefechts kommt es schon mal vor, das man sich vertut. Hier
1 | uint8_t* pI; |
2 | ...
|
3 | pI = 5; |
Lass uns mal die Datentypen vergleichen. Links vom = steht der Datentyp eines Pointers, rechts steht ein int. Und die beiden passen nun mal nicht zusammen. Ein int ist kein Adresse. Was war da jetzt wirklich gemeint? Wollte der Programmierer ein
1 | pI = (uint8_t*)5; |
oder wollte er ein
1 | *pI = 5; |
beides sind verschiedene Operationen. In den meisten Fällen wollte er eher letzteres und hat nur den * vergessen. Wenn du einen Pointer umcastest, dann kann man sagen, dass du umgangssprachlich dem Compiler mitteilst: Hör mal. Ich weiß, dass die Datentypen links und rechts nicht passen, aber das ist schon in Ordnung. Ich bin der Programmierer und ich hab mir das überlegt. Ich übernehme die Verantwortung dafür, dass das ok ist. Also nimm das beschissene Bitmuster und tu so, als ob das eine Adresse wäre. Und halt das Maul! Wenn du Pointer umcastest, passiert (in den meisten Fällen) nichts aufMaschinenebene. Der Cast hat einfach nur den Zweck, das der Compiler während der Abarbeitung des Ausdrucks gezwungen wird, kommentarlos den Wechsel im Datentyp zur Kentniss zu nehmen. Du kannst auch schreiben
1 | uint8_t * pPtr; |
2 | |
3 | pPtr = (uint8_t*) 5.0; |
wenn dir das Spass macht. Ob es Sinn macht, das interne Bitmuster für Floating Point Zahlen als Adresse aufzufassen, ist eine andere Frage. Den Compiler interessiert das allerdings nicht mehr. Denn ein derartiger Cast ist einfach nur ein: "Gusch, jetzt red ich. Du nimmst das Bitmuster rechts und weißt es an die Variable links zu. Und zwar ohne irgendeinen Kommentar" an den Compiler.
:
Bearbeitet durch User
Karl Heinz schrieb: > uint8_t * pPtr; > > pPtr = (uint8_t*) 5.0; Wir da wirklich das Bitmuster von der 5.0 als Adresse genommen? Oder wird aus der 5.0 erst eine 5 ? bei
1 | uint8_t i; |
2 | |
3 | i = (uint8_t) 5.0; |
wird ja auch nicht das Bitmuster genommen sondern der Wert 5 und em i zugewiesen.
Dirk B. schrieb: > Wir da wirklich das Bitmuster von der 5.0 als Adresse genommen? Oder > wird aus der 5.0 erst eine 5 ? Weder noch. Error: "A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type." Da konvertierende und reinterpretierende Casts in C nicht sauber getrennt sind, gibt es in C++ mittlerweile dafür separate Casts.
:
Bearbeitet durch User
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.