Forum: Mikrocontroller und Digitale Elektronik Frage zu Pointern (Arduino)


von Robert K. (mr_insanity)


Lesenswert?

Hallo,

ich habe ein unsigned char* irdata. Das bekommt einen Pointer auf einen 
"string" wie "NEC 0xBE414DB2 32" zugewiesen.
Diesen string teile ich auf mit
1
int i = 0;
2
char* arg[3];
3
4
char* irString = strdup(irData);
5
char* token = strtok(irString," ");
6
7
while(token != NULL){
8
  strcpy(arg[i], token);
9
  token = strtok(NULL, " ");
10
  i++;
11
}

Wenn ich das danach ausgebe mit
1
Serial.print("Protocol:"); Serial.print(*arg[0]);
2
Serial.print(" Code:"); Serial.print(*arg[1]);
3
Serial.print(" Bits:"); Serial.print(*arg[2]);
bekomme ich
1
Protocol:N Code:0 Bits:3

Lasse ich die Sternchen weg
1
Serial.print("Protocol:"); Serial.print(arg[0]);
2
Serial.print(" Code:"); Serial.print(arg[1]);
3
Serial.print(" Bits:"); Serial.print(arg[2]);
bekomme ich, wie gewollt
1
Protocol:NEC Code:0xBE414DB2 Bits:32

Wie Pointer funktionieren ist mir eigentlich klar. Aber hier verstehe 
ich es nicht. Warum bekomme ich bei der Version mit Sternchen nur 
jeweils die ersten Zeichen?

Gruß
Robert

von The D. (thedaz)


Lesenswert?

Weil der * operator den pointer derefenziert und die Speicherstelle 
ausliest, auf die der pointer zeigt. Der Inhalt dieser Speicherstelle 
ist genau ein character.

Alles weitere kannst du hier nachlesen : 
http://www.lysator.liu.se/c/bwk-tutor.html#pointers

: Bearbeitet durch User
von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Durch strcpy() wird übrigens nicht vorhandener Speicher überschrieben. 
Im Gegensatz zu strdup() alloziert strcpy() nämlich keinen neuen 
Speicher.

von Georg G. (df2au)


Lesenswert?

Ein String ist in C immer ein Zeichen länger als erwartet, weil noch ein 
0x00 als Ende-Kennung angehängt wird. Daher solltest du dein arg[3] noch 
einmal überdenken.

Mehr Platz als notwendig zu reservieren, hat selten geschadet. Aber zu 
wenig reservierter Platz und darauf folgendes Schreiben irgendwo im 
Speicher kann zu längeren Debug Sitzungen führen.

: Bearbeitet durch User
von Robert K. (mr_insanity)


Lesenswert?

Besten Dank.

Ist es also so richtig?:
*arg[0] ist der Wert des ersten Zeichens von "NEC". Damit wird nur das N 
ausgegeben.
arg[0] zeigt auf das erste Byte von "NEC" und Serial.print() gibt das 
Array von dort aus aus bis es \0 gefunden hat.

Aber was genau bedeutet nun dereferenzieren?
Für mich hört es sich an, als würde der pointer seine Referenz auf die 
Variable verlieren. Das ist doch aber nicht der Fall, oder?
*pointer gibt doch einfach den Wert der Variablen aus, auf die pointer 
zeigt.

von Robert K. (mr_insanity)


Lesenswert?

Georg G. schrieb:
>Daher solltest du dein arg[3] noch einmal überdenken.

Warum denn das? char* arg[3] ist ein Array von drei Pointern und ich 
habe 3 Teilstrings. Das passt doch.

von Luggi F. (luggi_f)


Lesenswert?

Robert K. schrieb:
> *pointer gibt doch einfach den Wert der Variablen aus, auf die pointer
> zeigt.

Und das heißt einfach "dereferenzieren" :-)

von The D. (thedaz)


Lesenswert?

Robert K. schrieb:
> Georg G. schrieb:
>>Daher solltest du dein arg[3] noch einmal überdenken.
>
> Warum denn das? char* arg[3] ist ein Array von drei Pointern und ich
> habe 3 Teilstrings. Das passt doch.

Und wo zeigen die 3 Pointer hin ?

von Robert K. (mr_insanity)


Lesenswert?

The D. schrieb:
> Und wo zeigen die 3 Pointer hin ?

Na das bekommen sie doch durch strtok() zugewiesen.
Oder worauf willst du hinaus?

von The D. (thedaz)


Lesenswert?

Robert K. schrieb:
> The D. schrieb:
>> Und wo zeigen die 3 Pointer hin ?
>
> Na das bekommen sie doch durch strtok() zugewiesen.
> Oder worauf willst du hinaus?

Und danach machst du strcpy(arg[i], token). Damit kopiert strcpy den 
String auf den token zeigt an die Speicherstelle auf die arg[i] zeigt. 
Also ins Nirvana weil die pointer nicht initialisiert sind. Du hast 
Glück, dass das Programm ueberhaupt was tut.

: Bearbeitet durch User
von Georg G. (df2au)


Lesenswert?

Robert K. schrieb:
> ein Array von drei Pointern und ich
> habe 3 Teilstrings. Das passt doch.

Wesentlich sinnvoller wäre Platz für 3 Strings, also arg[3][32] als 
Beispiel (und dann natürlich den Rest der Kopierorgie entsprechend 
anpassen).

von Robert K. (mr_insanity)


Lesenswert?

The D. schrieb:
> Und danach machst du strcpy(arg[i], token). Damit kopiert strcpy den
> String auf den token zeigt an die Speicherstelle auf die arg[i] zeigt.
> Also ins Nirvana weil die pointer nicht initialisiert sind. Du hast
> Glück, dass das Programm ueberhaupt was tut.

Ok, und wie sollte ich sie am besten initialisieren?
Ich habe gelesen, man sollte pointer immer mit 0 initialisieren.
Aber wenn ich das mach resettet der Controller wenn er in die while 
Schleife kommt.

von The D. (thedaz)


Lesenswert?

Georg G. schrieb:
> Robert K. schrieb:
>> ein Array von drei Pointern und ich
>> habe 3 Teilstrings. Das passt doch.
>
> Wesentlich sinnvoller wäre Platz für 3 Strings, also arg[3][32] als
> Beispiel (und dann natürlich den Rest der Kopierorgie entsprechend
> anpassen).

Nein, sinnvoller wäre es, den arg array aus const pointern bestehen zu 
lassen, alle Elemente hübsch auf NULL zu initialisieren, ihnen das 
Ergebnis der strtok Operation zuzuweisen und am Ende nicht das free auf 
irString zu vergessen.

von The D. (thedaz)


Lesenswert?

Robert K. schrieb:
> The D. schrieb:
>> Und danach machst du strcpy(arg[i], token). Damit kopiert strcpy den
>> String auf den token zeigt an die Speicherstelle auf die arg[i] zeigt.
>> Also ins Nirvana weil die pointer nicht initialisiert sind. Du hast
>> Glück, dass das Programm ueberhaupt was tut.
>
> Ok, und wie sollte ich sie am besten initialisieren?
> Ich habe gelesen, man sollte pointer immer mit 0 initialisieren.
> Aber wenn ich das mach resettet der Controller wenn er in die while
> Schleife kommt.

Genau, weil die pointer nämlich auf keinen Speicher zeigen. Entweder du 
weist allen drei pointern die Adresse eine ausreichend langen char 
arrays zu, oder du benutzt kein strcpy().

von Georg G. (df2au)


Lesenswert?

The D. schrieb:
> das
> Ergebnis der strtok Operation zuzuweisen

Und wie lang sind dann die Teilstrings, wo enden sie? Das wird nicht 
ganz das gewünschte Ergebnis erzielen.

von The D. (thedaz)


Lesenswert?

Georg G. schrieb:
> The D. schrieb:
>> das
>> Ergebnis der strtok Operation zuzuweisen
>
> Und wie lang sind dann die Teilstrings, wo enden sie? Das wird nicht
> ganz das gewünschte Ergebnis erzielen.

Lies mal die Beschreibung von strtok durch. strtok modifiziert den 
Original-String und fuegt null chars an die Stelle von whitespace ein. 
Der gelieferte Zeiger zeigt also auf den Speicher des Originalstrings + 
ggf einen offset.

von Dirk B. (dirkb2)


Lesenswert?

1
int i = 0;
2
char* arg[3];
3
4
char* irString = strdup(irData);
5
char* token = strtok(irString," ");
6
7
while(token != NULL){
8
//  strcpy(arg[i], token); falsch
9
  arg[i] = token;
10
  token = strtok(NULL, " ");
11
  i++;
12
}
arg[i] zeigt auf Strings innerhalb von irString.

Solange irString nicht durch free freigegeben wird (was irgendwann 
pasieren muss), sind auch die Zeiger von arg gültig.

: Bearbeitet durch User
von Robert K. (mr_insanity)


Lesenswert?

The D. schrieb:
> Lies mal die Beschreibung von strtok durch. strtok modifiziert den
> Original-String und fuegt null chars an die Stelle von whitespace ein.
> Der gelieferte Zeiger zeigt also auf den Speicher des Originalstrings +
> ggf einen offset.
Stimmt. Ist hier 
http://www2.informatik.uni-halle.de/lehre/c/c_strtok.html, wie ich 
finde, recht verständlich beschrieben.

Dirk B. schrieb:
>
1
> while(token != NULL){
2
> //  strcpy(arg[i], token); falsch
3
>   arg[i] = token;
4
>   token = strtok(NULL, " ");
5
>   i++;
6
> }
7
>
Macht Sinn wenn ich hier 
http://www2.informatik.uni-halle.de/lehre/c/c_strtok.html lese.
arg[i] erhält einfach den gleichen Wert wie token und zeigt somit auf 
den gleichen Speicherbereich.
Mit strcpy() wird der Inhalt des Speichers kopiert und je nach dem wo 
arg[0] hin zeigt könnte es z.B. von arg[1] überschrieben werden.
Richtig so?

von The D. (thedaz)


Lesenswert?

Robert K. schrieb:
> Mit strcpy() wird der Inhalt des Speichers kopiert und je nach dem wo
> arg[0] hin zeigt könnte es z.B. von arg[1] überschrieben werden.
> Richtig so?

Theoretisch möglich, aber wahrscheinlicher ist, dass das Programm 
abstürzt, weil schon der Versuch an die Adresse in arg[0] einen String 
zu kopieren etwas schief geht. Ganz generell ist strtok so sinnvoll wie 
ein Blinddarm. Die Funktion modifiziert den Quellstring und speichert 
sich in einer internen Variablen, wo die vorherige Operation aufgehört 
hat. Damit ist es unmöglich strtok gleichzeitig auf mehreren Strings 
anzuwenden. Außerdem würde ich davon abraten auf einem Mikrocontroller 
mit beschränkten Ressourcen wie dem Arduino den heap zu benutzen, also 
z.B. malloc, free und strdup. Besser du deklarierst alle notwendigen 
Variablen direkt selbst und behältst damit die Kontrolle über den 
verbrauchten Speicher.

von Dirk B. (dirkb2)


Lesenswert?

Du kannst den String auch mit sscanf lesen.

von Robert K. (mr_insanity)


Lesenswert?

Besten Dank für die Tips!

The D. schrieb:
> Ganz generell ist strtok so sinnvoll wie
> ein Blinddarm. Die Funktion modifiziert den Quellstring und speichert
> sich in einer internen Variablen, wo die vorherige Operation aufgehört
> hat. Damit ist es unmöglich strtok gleichzeitig auf mehreren Strings
> anzuwenden.
Bei mir bekommt es immer nur den einen String. Daher sollte das kein 
Problem sein.

The D. schrieb:
> Außerdem würde ich davon abraten auf einem Mikrocontroller
> mit beschränkten Ressourcen wie dem Arduino den heap zu benutzen, also
> z.B. malloc, free und strdup. Besser du deklarierst alle notwendigen
> Variablen direkt selbst und behältst damit die Kontrolle über den
> verbrauchten Speicher.
Eigentlich würde ich es jetzt gern so lassen. Funktioniert ja. Oder 
meinst du es könnte ernsthaft zu einem Problem werden? Das ganze ein 
MySensors Node zum Senden und Empfangen von IR-Signalen. Der bekommt nur 
ab und an mal ein paar Strings geschickt um 2-3 Geräte ein- bzw. 
auszuschalten.
Ich kann das leider überhaupt nicht einschätzen.
Letztendlich werden die Variablen und Zeiger beim Aufruf der 
Empfangsroutine jedes mal neu initialisiert. Das heißt doch eventuell 
benutzter Speicher wird wieder überschrieben. Nach meinem Verständnis 
sollte das dann keine Probleme geben.

von The D. (thedaz)


Lesenswert?

Robert K. schrieb:
>
> Bei mir bekommt es immer nur den einen String. Daher sollte das kein
> Problem sein.

Solange du immer nur einen String zur Zeit zerlegst ist das ok.

>
> Eigentlich würde ich es jetzt gern so lassen. Funktioniert ja. Oder
> meinst du es könnte ernsthaft zu einem Problem werden? Das ganze ein
> MySensors Node zum Senden und Empfangen von IR-Signalen. Der bekommt nur
> ab und an mal ein paar Strings geschickt um 2-3 Geräte ein- bzw.
> auszuschalten.
> Ich kann das leider überhaupt nicht einschätzen.
> Letztendlich werden die Variablen und Zeiger beim Aufruf der
> Empfangsroutine jedes mal neu initialisiert. Das heißt doch eventuell
> benutzter Speicher wird wieder überschrieben. Nach meinem Verständnis
> sollte das dann keine Probleme geben.

Du musst au jeden Fall an der richtigen Stelle in deinem code noch 
free(irString); aufrufen oder dein uC wird im Nu nicht mehr genug 
Speicher haben. Dann liefert strdup einen Null pointer zurueck und dir 
fliegt dein Programm um die Ohren.

Der Rat den heap nicht zu benutzen heißt nicht, dass es falsch ist ihn 
zu benutzen. Nur wenn du dir als Anfänger schon über die Funktion von 
pointern im Unklaren bist solltest du diesen Bereich ersteinmal meiden.

: 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.