Forum: Compiler & IDEs Frage zu Zeiger: Cast?


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von schlau-werden-Wollender (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Diesen Schnipsel hab ich hier (Programmbeispiel DMA für Raspberry PI):
int main() {
    volatile uint32_t *gpioBaseMem, *dmaBaseMem, *pwmBaseMem, *timerBaseMem, *clockBaseMem;

         .
         .
         .

    int outPin = 10;
    //now set our pin as an output:
    volatile uint32_t *fselAddr = (volatile uint32_t*)(gpioBaseMem + GPFSEL0/4 + outPin/10);
    writeBitmasked(fselAddr, 0x7 << (3*(outPin%10)), 0x1 << (3*(outPin%10)));

Was hat es mit der vorletzten Zeile auf sich? Für mich sieht das aus als 
ob der Zeiger *gpioBaseMem auf *gpioBaseMem gecasted wird - was ich für 
wirres Zeug halten würde. Oder hat das seinen Grund?

von ccccccc (Gast)


Bewertung
0 lesenswert
nicht lesenswert
naja outPin ist vom Datentyp int. GPSEL0 wird nicht klar was das für ein 
Datentyp ist. Da es in der 2ten Klammer der vorletzten Zeile zu einer 
implizieten Typumwandlung kommt, wandelt man mit (volatitle uint32_t*) 
wieder zurück zum richtigen Typ. Kann man durchaus so machen.

von MWS (Gast)


Bewertung
0 lesenswert
nicht lesenswert
schlau-werden-Wollender schrieb:
> Für mich sieht das aus als
> ob der Zeiger *gpioBaseMem auf *gpioBaseMem gecasted wird - was ich für
> wirres Zeug halten würde. Oder hat das seinen Grund?

Ich hätte jetzt gesagt, dass die geklammerten Berechnungen für den 
Zeiger mit einem Defaulttyp ausgeführt werden. Da dieser sich von der 
Zeigerbreite unterscheiden kann, teilt man dem Compiler per Cast mit, 
welche Bitbreite er für temporäre Ergebnisse verwenden soll.

ccccccc schrieb:
> naja outPin ist vom Datentyp int. GPSEL0 wird nicht klar was das für ein
> Datentyp ist.

schlau-werden-Wollender schrieb:
> mit der vorletzten Zeile

von Rainer B. (katastrophenheinz)


Bewertung
0 lesenswert
nicht lesenswert
sieht für mich aus wie ganz normale Zeigerarithmetik:
fSelAddr = gPioBaseMem + irgendwie berechneter Offset
Der Typecast davor dient dazu, auch das Ergebnis 'volatile' zu machen.

von schlau-werden-Wollender (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Autor: ccccccc (Gast) schrieb:
>GPSEL0 wird nicht klar was das für ein
>Datentyp ist.

Das gehört noch dazu:
#define GPFSEL0   0x00000000 //gpio function select. There are 6 of these (32 bit registers)

Sehe ich das richtig: Du meinst, daß:
int outPin = 10;
den Zeiger hier;
uint32_t *gpioBaseMem
auf ein int castet? Oder es zumindest zu einer Warnung/Fehlermeldung 
kommt?

von ccccccc (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Der Cast muss ja nicht unbedingt gemacht werden. Sprich
(gpioBaseMem + GPFSEL0/4 + outPin/10)
kann auch den richtigen Datentyp zurückgeben, das weiß ich jetzt nicht 
auswendig. Allerdings ist es schöner das ganze nochmal explizit 
darzustellen, dass das Ergebniss der Berechnung nochmal mit
(volatile uint32_t*)
zu casten.
So seh ich das zumindest, jetzt kommt eh gleich wieder: Ja warum 
angeben, das wird alles unleserlich. Ich finde man kann solche casts 
durchaus nochmal machen. Nach dem Python-Grundsatz: "Explicit is better 
than implicit"

von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
Die wahrscheinlichste Erklärung dürfte sein, dass der Programmierer am 
Anfang gpioBaseMem noch nicht als Pointer zu volatile hatte und dann 
einfach überall ein volatile mit der Gieskanne ergänzt hat. Vielleicht 
hatte er auch irgendwelchen Ärger und dann einfach mal rumgecastet. Das 
kommt leider häufiger vor, als man denkt, dass wenn etwas nicht so 
funktioniert wie man sich das vorstellt, dass dann einfach mal wild 
rumgecastet wird.
Rein formal ist der Cast hier nicht notwendig.

von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
ccccccc schrieb:
> naja outPin ist vom Datentyp int. GPSEL0 wird nicht klar was das für ein
> Datentyp ist. Da es in der 2ten Klammer der vorletzten Zeile zu einer
> implizieten Typumwandlung kommt

Das ist Pointer Arithmetik. Das Ergebnis hat denselben Datentyp, wie ihn 
der Pointer hatte. Und der ist 'vaoltile uint32_t *'. Ganz ohne Cast.

: Bearbeitet durch Moderator
von schlau-werden-Wollender (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Was mich auch ein bisschen eingeschüchtert hat ist die Klammerung:
(gpioBaseMem + GPFSEL0/4 + outPin/10)
Von der Arithmetik her könnte man die Klammer doch auch weglassen, oder?

von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
schlau-werden-Wollender schrieb:
> Was mich auch ein bisschen eingeschüchtert hat ist die Klammerung:
>
> (gpioBaseMem + GPFSEL0/4 + outPin/10)
> 
> Von der Arithmetik her könnte man die Klammer doch auch weglassen, oder?

Dann wär der Cast noch viel unsinniger.
Denn ein Cast bindet am stärksten. Damit wäre
volatile uint32_t *fselAddr = (volatile uint32_t*)gpioBaseMem + GPFSEL0/4 + outPin/10;
dasselbe wie
volatile uint32_t *fselAddr = ((volatile uint32_t*)gpioBaseMem) + GPFSEL0/4 + outPin/10;

und da der Datentyp von gpioBaseMem bereits
    volatile uint32_t *gpioBaseMem, ...
ist, castet man eine Variable auf ihren eigenen Datentyp. Eine sinnlose, 
ja sogar gefährliche, Operation.

: Bearbeitet durch Moderator
von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
ccccccc schrieb:
> Der Cast muss ja nicht unbedingt gemacht werden. Sprich
>
> (gpioBaseMem + GPFSEL0/4 + outPin/10)
> 
> kann auch den richtigen Datentyp zurückgeben, das weiß ich jetzt nicht
> auswendig. Allerdings ist es schöner das ganze nochmal explizit
> darzustellen,

Das Problem an dieser Stelle ist dasselbe wie bei jedem Cast, bei dem 
ein Pointer umgecastet wird: Es ist eine Waffe, weil man de facto 
jegliche Typprüfung des Compilers damit abschaltet.
So etwas macht man nicht, wenn man nicht unbedingt muss!

> durchaus nochmal machen. Nach dem Python-Grundsatz: "Explicit is better
> than implicit"

... but not, if you tell the compiler: I don't care if I make a mistake. 
I want you to just shut up!

: Bearbeitet durch Moderator
von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
schlau-werden-Wollender schrieb:
> Autor: ccccccc (Gast) schrieb:
>>GPSEL0 wird nicht klar was das für ein
>>Datentyp ist.
>
> Das gehört noch dazu:
>
> #define GPFSEL0   0x00000000 //gpio function select. There are 6 of 
> these (32 bit registers)
> 
>
> Sehe ich das richtig: Du meinst, daß:
> int outPin = 10;
> den Zeiger hier;
> uint32_t *gpioBaseMem
> auf ein int castet?


Jungs.
Ihr müsst schon die C Regeln lernen, wenn ihr C programmieren wollt!
Wie Pointer-Arithmetik in C funktioniert, ist klar geregelt. Ohne 
Pointer Arithmetik bleiben dann auch die Feinheiten von Arrays bzw. 
Arrayzugriffe auf immer ein Buch mit 7 Siegeln.

: Bearbeitet durch Moderator
von mameluk (Gast)


Bewertung
0 lesenswert
nicht lesenswert
[quote]
und da der Datentyp von gpioBaseMem bereits

    volatile uint32_t *gpioBaseMem, ...

ist, castet man eine Variable auf ihren eigenen Datentyp. Eine sinnlose,
ja sogar gefährliche, Operation.[/quote]
Sinnlos und überflüssig auf jeden Fall, aber wieso gefährlich?

von Karl H. (kbuchegg) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
mameluk schrieb:
> [quote]
> und da der Datentyp von gpioBaseMem bereits
>
>     volatile uint32_t *gpioBaseMem, ...
>
> ist, castet man eine Variable auf ihren eigenen Datentyp. Eine sinnlose,
> ja sogar gefährliche, Operation.[/quote]
> Sinnlos und überflüssig auf jeden Fall, aber wieso gefährlich?

Denk mal darüber nach, welche Falle sich auftut, wenn sich Datentypen 
ändern.
Gut, ist in diesem Fall nicht sehr wahrscheinlich. Aber im allgemeinen 
Fall.
int* dataTable;

.... dataTable kriegt Speicher und Werte


void foo()
{
  int* myData = (int*)dataTable;

  *myData = 8;
}

So weit so gut. Nichts spezielles passiert hier. Der Cast ist zwar 
überflüssig, ist aber an und für sich kein Problem.

Die Zeit vergeht und in ein paar Monaten stellt sich raus, dass 
dataTable auf einen double erweitert werden muss. Kein Problem, der 
Programmierer ändert den Datentyp
double* dataTable;

.... dataTable kriegt Speicher und Werte

und auf die Funktion vergisst er, bzw. das weiss er alles nicht mehr. Da 
steht nach wie vor
void foo()
{
  int* myData = (int*)dataTable;

  *myData = 8;
}

Tja. Und das geht in die Hose. Ohne Cast
void foo()
{
  int* myData = dataTable;

  *myData = 8;
}
hätte es vom Compiler einen Error gegeben. Denn links vom = steht der 
Datentyp 'Pointer auf int', rechts davon steht 'Pointer auf double' und 
die beiden können nicht einfach so zugewiesen werden, weil sie nicht 
kompatibel sind.
Mit dem Cast hat man aber dem Compiler effektiv gesagt: "Ich weiss, dass 
die Datentypen links und rechts nicht zusammen stimmen, das macht aber 
nichts. Ich bin der Programmierer und du hältst die Klappe!"
Und genau das macht der Compiler auch. Anstatt einen offensichtlichen 
Fehler zu melden (und die richtige Aktion wäre es natürlich den Datentyp 
von myData auf 'Pointer auf double' zu ändern) akzeptiert der Compiler 
das fehlerhafte Statement gezwungenermassen.

Wenn ein Pointer umgecastet wird, dann wird damit effektiv die 
Datentypprüfung abgeschaltet! Am numerischen Wert des Pointers ändert 
sich nichts, aber ein anderer Datentyp verursacht bei Derefenzierung 
eine andere Aktion (bzw. bei Adressarithmetik), weil da implizit immer 
die sizeof dessen auftaucht, worauf der Pointer zeigt.
Die Typprüfung des Compilers willst du aber nicht abschalten. Gerade bei 
Pointern nicht. Wenn 2 Pointer nicht datentypkompatibel sind, dann ist 
das (abgesehen von den Fällen, in denen ein volatile dazu oder 
weggecastet wird) in 980 von 1000 Fällen ein tatsächlicher Fehler! 
Natürlich kommt es vor, dass man Pointer auch mal umcasten muss. 
Speziell dann, wenn man an die Bytedarstellung von irgendwas ran muss. 
Aber abgesehen davon ist das meistens ein Fehler. Daher: Wenn da etwas 
nicht stimmt, dann willst du haben, dass der Compiler Feuer schreit. Und 
daher willst du den Compiler nicht mit einem Cast ruhig stellen. Auch 
nicht vorsorglich. Du schneidest dich damit im schlechtesten Fall nur 
ins eigene Fleisch und im besten Fall bewirkt der Cast genau gar nichts 
(so wie hier).

Wir alle wissen, dass man sich in C sehr leicht selbst ins Knie 
schiessen kann. Du willst jede Hilfe und jede Überwachung vom Compiler 
haben, die du kriegen kannst. Die mutwillig einfach abzuschalten, ist 
das genaue Gegenteil davon.

: Bearbeitet durch Moderator
von mameluk (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Danke für die ausführliche Antwort! :) Auf der Ebene war mir das Problem 
klar, ich dachte im vorliegenden Fall hätte ich irgendeine spezielle 
Falle übersehen.

Karl Heinz schrieb:
> Wir alle wissen, dass man sich in C sehr leicht selbst ins Knie
> schiessen kann. Du willst jede Hilfe und jede Überwachung vom Compiler
> haben, die du kriegen kannst. Die mutwillig einfach abzuschalten, ist
> das genaue Gegenteil davon.

Jepp!

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.