Forum: Mikrocontroller und Digitale Elektronik Ach ich blicke bei den Pointern nicht durch :-(


von Rene K. (xdraconix)


Lesenswert?

Ich habe da mal wieder ein Verständigungsproblem zwischen Mir, dem 
Rechner und C.

Ein Pointer ist doch ein Zeiger auf die Stelle der Variable, richtig? 
Also wenn ich eine float Variable - 32Bit breit habe und ich sie in eine 
Funktion übergeben, ist die "Übergabeadresse" (Wie nennt man das 
richtig? Ist das der Pointer?) ja 16Bit breit, richtig?

1
void MeineFunktion(uint16_t *pvalue){}

Aufrufen tue ich das ganze mit:

1
MeineFunktion(&MeineVariable);

Ist das soweit korrekt?

Dies scheint auch zu funktionieren, nun komme ich aber zu meinem 
Problem. In dieser Funktion lese ich auf die Adresse 2x8Bit ein, quasi:

1
void MeineFunktion(uint16_t *pvalue)
2
{
3
*(p_value)  =ReadMSB(1); 
4
*(p_value+1)=ReadLSB(1); 
5
}

Und hier hakt es... da kommen völlig falsche Werte dabei raus. Kann mir 
jemand erklären wo ich anfangen könnte zu suchen?!

Lübsten Dank. :-)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Du hast nur eine Variable, schreibst aber in zwei ...

Deine Funktion "MeineFunktion" geht davon aus, die Anfangsadresse eines 
Arrays übergeben zu bekommen, in dem mindestens zwei uint16_t Platz 
haben.
1
void MeineFunktion(uint16_t *pvalue)
2
{
3
*(p_value)  =ReadMSB(1);  // beschreibt pvalue[0], also das erste Arrayelement
4
*(p_value+1)=ReadLSB(1);  // beschreibt pvalue[1], also das zweite Arrayelement 
5
}

Das ist sicher nicht das, was Du vorhast.

von Nop (Gast)


Lesenswert?

> Ein Pointer ist doch ein Zeiger auf die Stelle der Variable, richtig?

Ja.

> Also wenn ich eine float Variable - 32Bit breit habe und ich sie in eine
> Funktion übergeben, ist die "Übergabeadresse" (Wie nennt man das
> richtig? Ist das der Pointer?) ja 16Bit breit, richtig?

Nein. Die Breite des Pointers hat mit der Variable nichts zu tun, 
sondern mit Deiner CPU und Deinem System.

Außerdem ist ein float 32 bit breit.

> void MeineFunktion(uint16_t *pvalue){}

Wenn der Wert ein float sein soll, dann schreib auch einfach "float *" 
hin.

> Ist das soweit korrekt?

Nein, nicht wenn Deine Variable ein float ist.

> void MeineFunktion(uint16_t *pvalue)
> {
> *(p_value)  =ReadMSB(1);
> *(p_value+1)=ReadLSB(1);

Befasse Dich ein wenig mit Pointer-Arithmetik. Dein pvalue zeigt auf 
uint16_t, d.h. pvalue+1 zeigt nicht etwa, wie Du offenbar annimmst, auf 
das nächste Byte im Speicher. Es zeigt auf den nächsten uint16_t.

Also praktisch heißt das, daß in Bytes betrachtet "*(pvalue+1)" ZWEI 
Bytes weiterzählt, nicht eines. Weil +1 in Pointerarithmetik nicht um 
Bytes weiterzählt, sondern um soviele Bytes, wie der referenzierte 
Datentyp in Bytes groß ist. Da ein uint16_t zwei Bytes groß ist, in 
diesem Fall um zwei Bytes.

Richtig könnte es so gehen, unter der Annahme, daß ReasMSB/LSB Werte von 
0 bis 255 liefern:
1
*(p_value)  = (ReadMSB(1) << 8u) | ReadLSB(1);

Was die 1 bei dem Parameter bedeuten soll, weiß ich natürlich nicht, das 
sagst Du ja auch nicht.

von Johannes S. (Gast)


Lesenswert?

Hurra, der nächste Thread warum C Kacke ist :-(
Bitte gleich das Schloss dranmachen.

von Nop (Gast)


Lesenswert?

Johannes S. schrieb:
> urra, der nächste Thread warum C Kacke ist :-(

Wieso kacke?! Pointerarithmetik ist doch eines der besten Features von C 
überhaupt, zusammen mit Mehrfach-Dereferenzierung.

von Zeno (Gast)


Lesenswert?

Johannes S. schrieb:
> Hurra, der nächste Thread warum C Kacke ist :-(

Warum das wohl so ist?

Keine Angst vor endlosen Diskussionen ich steige sofort aus dem Thread 
aus! Versprochen!

von Rene K. (xdraconix)


Lesenswert?

Ach liebsten Dank ich habe verstanden! :-D

Ja wie gesagt, ich dachte das dort ausschließlich die Adresse übergeben 
wird - in der Wertbreite des Prozessor. Dies ist aber nicht der Fall.

Ich werde mich da unbedingt nochmal einlesen müssen. Aber ihr habt mir 
da schon ziemlich weitergeholfen!

Nop schrieb:
> Was die 1 bei dem Parameter bedeuten soll, weiß ich natürlich nicht, das
> sagst Du ja auch nicht.

Achso, war ein Copy&Paste Überbleibsel aus dem Code. Das ist von einer 
SHT Routine, wo dieses Problem auftrat. So sieht sie richtig aus - mit 
der 1 oder 0 gebe ich einen ACK oder noACK an den Sensor:

1
uint8_t SHT_measure(float *p_value, uint8_t *p_checksum, uint8_t mode)
2
{
3
   uint8_t error = 0;  
4
   SHT_startTrans();
5
6
   switch(mode)    
7
   { 
8
      case 1 : error+=SHT_write_byte(MEASURE_TEMP); break;
9
      case 2 : error+=SHT_write_byte(MEASURE_HUMI); break;
10
      default : break;
11
   }
12
13
   if(SHT_wait_to_complete()) error+=1; 
14
   *p_value = (SHT_read_byte(1) << 8u) | SHT_read_byte(1); 
15
   *p_checksum =SHT_read_byte(0);
16
   return error;
17
}

von Johannes S. (Gast)


Lesenswert?

Nop schrieb:
> Johannes S. schrieb:
> urra, der nächste Thread warum C Kacke ist :-(
>
> Wieso kacke?! Pointerarithmetik ist doch eines der besten Features von C


Jepp, wie ein Messer. Damit kann man Brot schneiden aber auch Unfug 
mach. Kommt eben auf den Anwender an. Und wieviel Schutz und Regeln und 
Überwacher er braucht.
Für auf Nummer sicher gehen : auf jeden Fall Warnungen als Fehler 
betrachten.

von Nop (Gast)


Lesenswert?

Rene K. schrieb:

> uint8_t SHT_measure(float *p_value
...
>    *p_value = (SHT_read_byte(1) << 8u) | SHT_read_byte(1);

Das geht so nicht. float ist ein Fließkommawert mit einer ganz eigenen 
Darstellung. Der hat einige Bits für die Mantisse und einige für den 
Exponenten, vereinfacht gesagt. Damit möchte man nicht rummmachen, wenn 
man sich nicht sehr eingehend mit dem Bitformat befaßt hat.

Also wenn Du nicht wirklich Fließkomma brauchst, sondern nur Highbyte 
und Lowbytes in zwei Byteports hast, dann mach das nicht als float, 
sondern als uint16_t.

von Nop (Gast)


Lesenswert?

Johannes S. schrieb:
> Jepp, wie ein Messer. Damit kann man Brot schneiden aber auch Unfug
> mach.

Ack. Und da ich keine stupfen Messer mag.. (:

> Für auf Nummer sicher gehen : auf jeden Fall Warnungen als Fehler
> betrachten.

Und obendrein auch noch z.B. CppCheck nehmen. Kostenlos und gut.

von Johannes S. (Gast)


Lesenswert?

Ja, guter Vorschlag. Der Compiler meckert normalerweise erst wenn es 
schon fast ein Fehler ist, diese Tools gehen weiter.
Ich bin noch ein Dino der ohne ausgekommen ist (haha), aber nur weil es 
das früher nicht gab. Die Zeit die man in solche Fehlerfrüherkennung 
steckt holt man aber in der -suche locker wieder raus.

von Rene K. (xdraconix)


Lesenswert?

Nop schrieb:
> Das geht so nicht. float ist ein Fließkommawert mit einer ganz eigenen
> Darstellung. Der hat einige Bits für die Mantisse und einige für den
> Exponenten, vereinfacht gesagt. Damit möchte man nicht rummmachen, wenn
> man sich nicht sehr eingehend mit dem Bitformat befaßt hat.
>
> Also wenn Du nicht wirklich Fließkomma brauchst, sondern nur Highbyte
> und Lowbytes in zwei Byteports hast, dann mach das nicht als float,
> sondern als uint16_t.

Das ist ein Argument, hab ich gleich umgesetzt. Ich brauche die 
Fließkommawerte erst später zur Berechnung:
1
typedef union
2
{
3
   uint16_t i;
4
   float f;
5
   uint8_t c;
6
} SHTvalue;
7
8
//....
9
10
uint8_t SHT_measure(uint16_t *p_value, uint8_t *p_checksum, uint8_t mode)
11
{
12
   uint8_t error = 0; 
13
   SHT_startTrans();    
14
15
   switch(mode)    
16
   { 
17
      case 1 : error+=SHT_write_byte(MEASURE_TEMP); break;
18
      case 2 : error+=SHT_write_byte(MEASURE_HUMI); break;
19
      default : break;
20
   }
21
22
   if(SHT_wait_to_complete()) error+=1;
23
   *p_value = (SHT_read_byte(1) << 8) | SHT_read_byte(1); 
24
   *p_checksum =SHT_read_byte(0);
25
   return error;
26
}
27
28
//....Aufruf
29
30
SHTvalue SHT11_Byte;
31
error += SHT_measure(&SHT11_Byte.i,&SHT11_Byte.c,1);
32
33
34
//....Umwandlung dann
35
36
SHT11_Byte.f = (float)SHT11_Byte.i;

von Nop (Gast)


Lesenswert?

Rene K. schrieb:
> typedef union
> {
>    uint16_t i;
>    float f;
>    uint8_t c;
> } SHTvalue;

Uhhh, also sowas würde ich auch weglassen. Ich verwette Steves Mops, daß 
Du Dir nicht exakt im Klaren bist, was genau diese Union tut, sofern Du 
sie zur Bitkonversion verwendest.

Du brauchst die Union auch eigentlich nicht. Du kannst auch einen 
plain-uint16_t mit dem (float)-cast in einen float umwandeln.
1
uint16_t x;
2
float y;
3
4
...
5
6
y = (float) y;

das funktioniert.

von Nop (Gast)


Lesenswert?

y = (float) x;

von Johannes S. (Gast)


Lesenswert?

Mit der Union legst du Variablen auf die gleiche Adresse, dein .c 
überschreibt also böse dein .i.
Du möchtest eher eine Struktur struct die alle Variablen gleichzeitig 
hält?

von Nop (Gast)


Lesenswert?

Johannes S. schrieb:
> Du möchtest eher eine Struktur struct die alle Variablen gleichzeitig
> hält?

Steves Mops dagegen gewettet. ^^

von Rene K. (xdraconix)


Lesenswert?

Nop schrieb:
> Rene K. schrieb:
>> typedef union
>> {
>>    uint16_t i;
>>    float f;
>>    uint8_t c;
>> } SHTvalue;
>
> Uhhh, also sowas würde ich auch weglassen. Ich verwette Steves Mops, daß
> Du Dir nicht exakt im Klaren bist, was genau diese Union tut, sofern Du
> sie zur Bitkonversion verwendest.
>
> Du brauchst die Union auch eigentlich nicht. Du kannst auch einen
> plain-uint16_t mit dem (float)-cast in einen float umwandeln.
> uint16_t x;
> float y;
>
> ...
>
> y = (float) y;
>
> das funktioniert.


Oh.. okay... ja gerade bemerkt :-D Union = gleicher Speicherplatz und 
Struct = unterschiedlicher Speicherplatz. Gleich mal durchprobiert 
beides.


Nop schrieb:
> Johannes S. schrieb:
>> Du möchtest eher eine Struktur struct die alle Variablen gleichzeitig
>> hält?
>
> Steves Mops dagegen gewettet. ^^

Jep, so war das mal gedacht. :x

Ich muss mich ganz dringend mal in die Variablen einarbeiten! Das hat 
mir der heutige Abend gelehrt. Bis dato kam ich ja ganz gut mit den 
normalen Zuweisungen und Bearbeitungen zurande. Will man aber etwas 
tiefer hinein, kommt man da nicht wirklich drumherum.

von Johannes S. (Gast)


Lesenswert?

Ja, Öl ins Feuer für 'einfache Sachen kann man in C auch kompliziert 
machen '.
SHT Dingsda sollte seine Id/Adresse sonstwas und den Zeiger auf das 
Ergebnis als Argument bekommen und Fehler als Return Wert is ok.
Fehler addieren geht nur wenn es Bitpos. sind, sonst isses nich 
eindeutig.

Gute Nacht.

von Johannes S. (Gast)


Lesenswert?

Rene K. schrieb:
> Ich muss mich ganz dringend mal in die Variablen einarbeiten!

Nicht schlimm, das ist der normale Leidensweg in C, deshalb mag ich die 
Cortex-M wo debugging einfach und billig möglich ist.
Dein Programm hätte je nach Werten sogar funktioniert, aber das ist noch 
böser. Denn ein funktionierendes Programm hat nach Murphy versteckte 
Fehler! :-/

von Nop (Gast)


Lesenswert?

Rene K. schrieb:
> Bis dato kam ich ja ganz gut mit den
> normalen Zuweisungen und Bearbeitungen zurande. Will man aber etwas
> tiefer hinein, kommt man da nicht wirklich drumherum.

Also eine gute Faustregel ist, im Normalfall immer bei den Datentypen zu 
bleiben, die man gerade hat. Jede Typkonversion bietet Fallen. Gerade in 
C, damit muß man sich eingehend befassen. Aber solange man dabei bleibt, 
was man gerade hat, ist da auch kein Risiko. Wie mit Frauen. (;

Und Unions sind definitiv für Fortgeschrittene.

Kann gelegentlich seinen Sinn haben, beispielsweise wenn man ein Struct 
aus 4 Bytes hat. Da kann man eine Union drüberlegen, wenn man dieses 
Struct schnell auf Identität oder auf Null prüfen will. Aber sonst, 
Finger weg von Unions.

von Mark B. (markbrandis)


Lesenswert?

Johannes S. schrieb:
> Hurra, der nächste Thread warum C Kacke ist :-(
> Bitte gleich das Schloss dranmachen.

Wenn man einen Hammer verkehrt herum hält und mit dem Stiel auf den 
Nagel einschlägt, ist dann das Werkzeug daran schuld?

von Rene K. (xdraconix)


Lesenswert?

Soo.. also diese Union Geschichte lässt mich da gerade nicht mehr los 
:-D Wenn man darüber nachdenkt, ist das ja eigentlich eine ganz feine 
Sache. Wäre es damit auch möglich auch Bit-Weise auf die selbe Variable 
zuzugreifen? Weil ich definiere ja einen Speicherbereich z.b. 1 Byte, 
diesen Fülle ich ja mit einem Wert, die darauffolgenden Union Member 
greifen ja auf diesen Bereich auch zu oder? Also ich meine das so?

1
typedef uinion {
2
   uint8_t u_value;
3
4
   uint8_t Bit0 : 1;
5
   uint8_t Bit1 : 1;
6
   uint8_t Bit2 : 1;
7
   uint8_t Bit3 : 1;
8
   uint8_t Bit4 : 1;
9
   uint8_t Bit5 : 1;
10
   uint8_t Bit6 : 1;
11
   uint8_t Bit7 : 1;
12
} BitValue

Oder muss ich das irgendwie in einem uinion in eine Struct stecken?
1
typedef uinion {
2
   uint8_t u_value;
3
4
   struct {
5
      uint8_t Bit0 : 1;
6
      uint8_t Bit1 : 1;
7
      uint8_t Bit2 : 1;
8
      uint8_t Bit3 : 1;
9
      uint8_t Bit4 : 1;
10
      uint8_t Bit5 : 1;
11
      uint8_t Bit6 : 1;
12
      uint8_t Bit7 : 1;
13
   }
14
} BitValue

Wäre das machbar? Wenn ja - genial!

von Garden (Gast)


Lesenswert?

Hier mal ein Video zum Thema Pointer in C:

http://et-tutorials.de/3368/pointer-in-c/

von S. R. (svenska)


Lesenswert?

Ja, das kannst du mit der Struct so machen.
Nein, was du tun willst, ist nicht eindeutig im Standard definiert.
Nein, für Hardwareregister ist das auch keine gute Lösung.
Wenn du Bitfelder in C anfasst, bist du ganz schnell
im Bereich der "implementation-defined"ness.

von Nop (Gast)


Lesenswert?

Rene K. schrieb:
> Wäre es damit auch möglich auch Bit-Weise auf die selbe Variable
> zuzugreifen?

Nein, das geht mit Bitfields nicht, weil der C-Standard dem Compiler 
nicht vorschreibt, wie herum er die Bits in einem Bitfield legen soll. 
Also das zuerst definierte Bit (bit 0) kann je nach Compiler mal das 
höchstwertigste oder auch das niederwertigste Bit der Variablen sein. 
Oder auch in der Mitte, theoretisch.

Bitfields darf man überhaupt nur über den Namen der Elemente ansprechen, 
alles andere ist nicht garantiert. Deswegen sind Bitfields ja auch für 
Register-Bitmapping vollkommen verkehrt.

Was aber geht:

Nimm eine Union z.B. aus einem uint32_t und beliebigen anderen 
32bit-Datentypen. Dann kannste da irgendwas reinfüllen, etwa einen 
float, und dann kannste den uint32 ansprechen. Dessen Bits kriegste mit 
Bitmaskierung raus, also etwas wie (my_uint32 & 0x00000001ul) für Bit 0.

Unions sind seit C99 ja für type punning erlaubt und auch das einzige 
Mittel dafür, weil pointer based type punning wegen pointer aliasing 
nicht erlaubt ist und wegoptimiert werden kann.

von Rene K. (xdraconix)


Lesenswert?

Garden schrieb:
> Hier mal ein Video zum Thema Pointer in C:
>
> http://et-tutorials.de/3368/pointer-in-c/

Danke, werde ich mir mal ansehen. So die Grundzüge der Pointer hatte ich 
die Nacht noch begriffen :-)

S. R. schrieb:
> Nein, für Hardwareregister ist das auch keine gute Lösung.

Nein, der Sinn der mich gerade da verfolgte ist ein Register in einer 
ausgelesenen Variable, welches ich ja somit relativ leicht abbilden kann 
ohne Bitschieberei.

Danke für die Hinweise :-D

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Rene K. schrieb:
> void MeineFunktion(uint16_t *pvalue)
> {
> *(p_value)  =ReadMSB(1);
> *(p_value+1)=ReadLSB(1);
> }


Wah! wenn man einen uint16-pointer inkrementiert, dann wird die Adresse 
um 2 Byte erhöht ... Man muss ihn quasi erstmal in einen char-Pointer 
casten, wenn man es unbedingt so machen möchte ...

Quasi
*((uint8*) p_value) = ...
*(((uint8*) p_value)+1) = ...

von Draco (Gast)


Lesenswert?

Mampf F. schrieb:
> Wah! wenn man einen uint16-pointer inkrementiert, dann wird die Adresse
> um 2 Byte erhöht ... Man muss ihn quasi erstmal in einen char-Pointer
> casten, wenn man es unbedingt so machen möchte ...
>
> Quasi
> *((uint8*) p_value) = ...
> *(((uint8*) p_value)+1) = ...

Ja, falls du den Thread weiter verfolgt hast, wurde das Problem bereits 
erklärt, der TS hat es verstanden und implementiert.

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.