Forum: Mikrocontroller und Digitale Elektronik Fragen zu C / embedded C


von Zweistein (Gast)


Lesenswert?

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
von (prx) A. K. (prx)


Lesenswert?

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
von B. S. (bestucki)


Lesenswert?

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
von Kaj (Gast)


Lesenswert?

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'

von (prx) A. K. (prx)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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
von Daniel A. (daniel-a)


Lesenswert?

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};

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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};

;)

von Zweistein (Gast)


Lesenswert?

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!

von B. S. (bestucki)


Lesenswert?

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.

von Daniel A. (daniel-a)


Lesenswert?

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.

von Zweistein (Gast)


Lesenswert?

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?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Daniel A. schrieb:
> In c sind alle Funktionen global

Innerhalb eines Moduls. Auf Modulebene hingegen nur dann, wenn sie nicht 
als static deklariert sind.

von Karl H. (kbuchegg)


Lesenswert?

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
}

von Zweistein (Gast)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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
von Dirk B. (dirkb2)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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
Noch kein Account? Hier anmelden.