Forum: Mikrocontroller und Digitale Elektronik Bitte um Verständnishilfe bei Pointer in Funktion


von Dietmar Hersch (Gast)


Lesenswert?

Morgen liebe Leute, ich habe mal ein Verständnisfrage.

Und zwar geht es um Funktionen, die einen String bearbeiten - hier 
erstmal die Aufrufe:

Ich rufe die Funktion auf und übergebe einen Textstring:
1
lass_string_blinken ("TEST1");

Diese Funktion wird aufgerufen und bekommt den String:
1
void lass_string_blinken (*string)
2
{
3
  ...wenn AN-ZEIT...
4
  {
5
    sende_string_zum_lcd (string);
6
  }
7
8
  ...wenn AUS-ZEIT...
9
  {
10
    loesche_Zeile (entsprechnende_Zeile);
11
  }
12
}

Und diese unktion gibt den String aus:
1
void sende_string_zum_lcd (char *string)
2
{
3
  while (*string != 0)
4
  {
5
    lcd_send_alphanum (*string++);
6
  }
7
}

So...mein Frage ist nun, wie ist das mit den Pointern? So, wie es jetzt 
ist, funktioniert es einwandfrei. Aber es war mehr try-and-error. Ich 
habe jetzt schon recht lang versucht, mir diese Pointer-Arithmetik 
anzulesen, aber ich tue mich damit sehr schwer...ich raff einfach nicht, 
wann man Adressen un wann man Inhalte übergibt.

Im ersten Funktionsaufruf übergebe ich doch quasi automatisch die 
Anfangsadresse meines Strings "TEST1" an die Funktion zum Blinkenlassen.

Diese nimmt die Adresse als *string, also den Inhalt an der Adresse auf, 
oder sehe ich das falsch?

Von hier wird die Funktion zum Senden der einzelnen Zeichen aufgerufen, 
aber wieso übergebe ich den Parameter nun einfach als normale Variable, 
wo die Funktion zum Senden diese wieder als Pointer übernimmt?


Kann mir da einer vielleicht etwas Licht ins dunkel bringen? Mit den 
Zeigern tue ich mich leider nach wie vor etwas schwer...

Danke schonmal im Voraus!

von Karl H. (kbuchegg)


Lesenswert?

Dietmar Hersch schrieb:

> So...mein Frage ist nun, wie ist das mit den Pointern?

Gaaaanz einfach.

> Im ersten Funktionsaufruf übergebe ich doch quasi automatisch die
> Anfangsadresse meines Strings "TEST1" an die Funktion zum Blinkenlassen.

Es ist gaz einfach.
In C wird an eine Funktion immer per Value übergeben.
D.h. die Funktion bekommt den Wert selber

machst du
1
void foo( int i )
2
{
3
  i = 3;
4
}
5
6
int main()
7
{
8
  int j = 8;
9
10
  foo( j );
11
}

dann wird beim Aufruf der Funktion der Wert von j ermittelt (= 8) und 
dieser Wert an die Funktion übergeben. Die Funktion selbst erzeugt sich 
eine neue Variable namens i, in der dieser Wert abgelegt wird und somit 
innerhalb der Funktion zur Verfügung steht. Damit ist auch klar: wird i 
innerhalb der Funktion verändert, so hat dies keine Auswirkungen auf j. 
Die beiden sind in keiner Weise miteinander assoziiert.

Das ist der Standardmechanismus mit dem in C Argumente an Funktionen 
übergeben werden.

Mit einer Ausnahme:
Bei Arrays ist das anders. Arrays werden an Funktionen übergeben, indem 
die Startadresse des Arrays an die Funktion übergeben wird.
1
void foo( int* a )
2
{
3
  a[0] = 5;
4
  ...
5
}
6
7
int main()
8
{
9
  int j[5];
10
11
  foo( j );
12
}

Dadurch, dass die Funktion die Startadresse des Arrays j selber hat, 
kann die Funktion über diese Startadresse das Arrays selber verändern.

C-Strings sind auch nichts anderes als Arrays. Arrays von einzelnen 
Charactern, die zusammen genommen den Text ergeben.

> Diese nimmt die Adresse als *string, also den Inhalt an der Adresse auf,
> oder sehe ich das falsch?

Sie nimmt ihn in string auf, nicht *string. *strig wäre schon das 
Zeichen, auf das dieser Pointer zeigt.

> Von hier wird die Funktion zum Senden der einzelnen Zeichen aufgerufen,
> aber wieso übergebe ich den Parameter nun einfach als normale Variable,

weil string eine Pointer Variable ist und du den Pointer in string hast

> wo die Funktion zum Senden diese wieder als Pointer übernimmt?

Passt doch

string ist die Pointer Variable. Also übergibst du auch den Pointer.

> Kann mir da einer vielleicht etwas Licht ins dunkel bringen? Mit den

in

char * string

ist string die Variable, die die Adresse enthält! *string ist das 
Zeichen an dieser Adresse! Bei der Deklaration gehört der * logisch 
gesehen eher zum Datentyp als zur Variablen.

   int *j;

j ist ein Zeiger_auf_int.
Und nicht *j ist ein Zeiger auf int.

Syntaxmässig hat man sich aber entschieden, dass der * an den Namen der 
Variablen bindet, so dass

   int * j, i;

zwar j als Pointer deklariert, aber nicht i. Und daher schreiben viele 
Programmierer den * beim Variablennamen um da nicht hineinzufallen.

    int *j, *i;

C++ Programmierer tendieren des öfteren dazu, den * beim Datentyp zu 
lassen und diese Falle dadurch zu umgehen, dass sie ganz einfach nicht 
mehrere Variablen in einer Deklaration machen.

   int* j;
   int  i;

damit ist dann auch alles klar.

(Die C Sichtweise war:  In

    double *k;

erhält man einen double, wenn man auf k die Operation * anwendet.

   double l = *k;

Daher kommt diese etwas seltsame Semantik in Deklarationen, in der 
mehrere Variablen unterschiedliche Datentypen in ein und derselben 
Deklaration haben können.
    double *k, l, *m, *n, o;

k, m und n sind Pointer
l und o sind normale double Variablen.
)

Literatur und üben!

von Dietmar Hersch (Gast)


Lesenswert?

Dietmar Hersch schrieb:
> lass_string_blinken ("TEST1");

Was übergebe ich hier an die nächste Funktion? Adresse oder Inhalt?
Adresse, oder?



Dietmar Hersch schrieb:
> void lass_string_blinken (*string)

Diese Funktion nimmt die Adresse auf, das Argument ist nun aber *string, 
also ist der Inhalt gemeint? Oder hat das damit garnichts zu tun?



Dietmar Hersch schrieb:
> sende_string_zum_lcd (string);

Hier ist es ja nur wieder die Variable, allerdings war es vorher ja noch 
*string...diesmal übergebe ich aber nur string - was ist string an 
dieser Stelle? Adresse oder ist hier der Inhalt drin? Ich müsste ja 
eigentlich weiterhin die Adresse übergeben...



Dietmar Hersch schrieb:
> void sende_string_zum_lcd (char *string)
> {
>   while (*string != 0)
>   {
>     lcd_send_alphanum (*string++);

Und hier ist ja dann ganz klar der Inhalt gemeint! Weil es wird ja 
überprüft, ob der Inhalt nicht die '\0' ist und solange Zeichen für 
Zeichen übermittelt - also garantiert der Inhalt.

Trotzdem ist mir das ein kleines Rätsel - ich weiß nie genau, wann der 
Inhalt und wann die Adresse benutzt wird. Und ich weiß nicht, wieviel 
ich mittlerweile schon gelesen hab darüber, aber das gibt bei mir immer 
nur ne Hirnverknotung und irgendwann ist Schluss - ich denke mal, wenn 
man es einmal verstanden hat, dann isses bestimmt logisch, aber.....

Manchmal wird doch auch mit &variable eine Adresse vergeben, passiert 
dies bei einem String automatisch?

Vielen Dank auf jeden Fall schonmal für deinen Beitrag!

von Dietmar Hersch (Gast)


Lesenswert?

Dietmar Hersch schrieb:
> eine Adresse vergeben

"übergeben", sorry!

von Karl H. (kbuchegg)


Lesenswert?

Dietmar Hersch schrieb:
> Dietmar Hersch schrieb:
>> lass_string_blinken ("TEST1");
>
> Was übergebe ich hier an die nächste Funktion? Adresse oder Inhalt?
> Adresse, oder?

Adresse


> Dietmar Hersch schrieb:
>> void lass_string_blinken (*string)
>
> Diese Funktion nimmt die Adresse auf, das Argument ist nun aber *string,
> also ist der Inhalt gemeint? Oder hat das damit garnichts zu tun?

Das hat gar nichts damit zu tun.
Die korrekte Funktionssignatur wäre

void lass_string_blinken (char *string)

und das sagt lediglich aus, dass string eine Variable ist, nämlich eine 
Variable vom Datentyp: Pointer auf Character.


>> sende_string_zum_lcd (string);
>
> Hier ist es ja nur wieder die Variable

Das ist ja auch ein Funktionsaufruf.
Die Funktion send_string_zum_lcd wird aufgerufen und ihr wird der Inhalt 
der Variablen 'string' (welcher eine Adresse ist, weil string ja vom 
Datentyp 'Pointer auf Character' ist) übergeben.

, allerdings war es vorher ja noch
> *string...

void lass_string_blinken (char *string)

hier ist es ein Funktionsheader. Und hier muss festgelegt werden, 
welchen Datentyp string hat. Nämlich 'Pointer auf Character'


>> void sende_string_zum_lcd (char *string)
>> {
>>   while (*string != 0)
>>   {
>>     lcd_send_alphanum (*string++);
>
> Und hier ist ja dann ganz klar der Inhalt gemeint!

Richtig.
*string ist das Zeichen, das man erhält, wenn man unter der Adresse im 
Speicher nachsieht, die in string steht.

Du musst unterscheiden zwischen der Schreibweise bei der Definition 
einer Variablen (dort wird der Datentyp festgelegt) und der 
tatsächlichen Verwendung.

von Clyde S. (mr-bit)


Lesenswert?

Dietmar Hersch schrieb:
> Dietmar Hersch schrieb:
>> lass_string_blinken ("TEST1");
>
> Was übergebe ich hier an die nächste Funktion? Adresse oder Inhalt?
> Adresse, oder?
Japp, Du übergibt die Adresse des ersten Zeichens der Zeichenfolge.


> Dietmar Hersch schrieb:
>> void lass_string_blinken (*string)
>
> Diese Funktion nimmt die Adresse auf, das Argument ist nun aber *string,
> also ist der Inhalt gemeint? Oder hat das damit garnichts zu tun?
Das ist eine reine Deklaration zur Festlegung der im Funktionsbody 
benutzten lokalen Variablen, diese definiert die Variable "string" als 
einen "Pointer auf den Typ ???" ... da müsste der Compiler eigentlich 
meckern ;)


> Dietmar Hersch schrieb:
>> sende_string_zum_lcd (string);
>
> Hier ist es ja nur wieder die Variable, allerdings war es vorher ja noch
> *string...diesmal übergebe ich aber nur string - was ist string an
> dieser Stelle? Adresse oder ist hier der Inhalt drin? Ich müsste ja
> eigentlich weiterhin die Adresse übergeben...
Weiterhin die Adresse, die Deklaration von "sende_string_zum_lcd()" 
erwartet ja einen solchen.


> Dietmar Hersch schrieb:
>> void sende_string_zum_lcd (char *string)
>> {
>>   while (*string != 0)
>>   {
>>     lcd_send_alphanum (*string++);
>
> Und hier ist ja dann ganz klar der Inhalt gemeint! Weil es wird ja
> überprüft, ob der Inhalt nicht die '\0' ist und solange Zeichen für
> Zeichen übermittelt - also garantiert der Inhalt.
Japp, diesmal dient der "*" dazu den Pointer zu dereferenzieren, sprich 
den Wert an der Adresse zu ermitteln.


> Trotzdem ist mir das ein kleines Rätsel - ich weiß nie genau, wann der
> Inhalt und wann die Adresse benutzt wird. Und ich weiß nicht, wieviel
> ich mittlerweile schon gelesen hab darüber, aber das gibt bei mir immer
> nur ne Hirnverknotung und irgendwann ist Schluss - ich denke mal, wenn
> man es einmal verstanden hat, dann isses bestimmt logisch, aber.....
Ist eigentlich ganz einfach und nur ne Gewohnheitssache.
Willst Du einen Pointer übergeben musst Du dies mit einem "*" in der 
Deklaration dem Compiler verständlich machen, egal ob dies nun eine 
Variablen- oder Funktionsdeklaration ist (diese erzeugt intern ja auch 
nichts anderes als lokale Variablen).
Willst Du auf den Wert unter einem Pointer zugreifen musst Du explizit 
ein "*" voranstellen.


> Manchmal wird doch auch mit &variable eine Adresse vergeben, passiert
> dies bei einem String automatisch?
Es wird immer der Datentyp der Variablen genutzt - solang nicht mit 
expliziten Typecasts gearbeitet wird. Und da Deine Variable "string" als 
"char *" definiert wurde wird beim übergeben immer dieser Typ 
angenommen.

von M. B. (reisender)


Lesenswert?

Vielleicht noch ein simples Beispiel, welches dir helfen könnte:
1
int* a;   // Pointer auf int
2
int b = 3;
3
int c;
4
5
a = &b;   // a zeigt auf b. Mit & wird die Adresse von b "geholt"
6
c = *a;   // c enthält jetzt den Inhalt der Adresse, welche in a
7
          // gespeichert ist (also b, also 3)

Wie schon erwähnt wurde muss man erst einmal verstehen, dass * zwei 
Bedeutungen haben kann.

von Dietmar Hersch (Gast)


Lesenswert?

OK, besten Dank euch!

Das muss ich mir jetzt mal Schritt für Schritt klarmachen!

von P. S. (Gast)


Lesenswert?

Tip: Sammle ein paar Wochen Erfahrung mit Assembler. Dann sollte dir 
einiges mehr darueber klar sein, was Adressen & Pointer eigentlich sind.

von Dietmar Hersch (Gast)


Lesenswert?

Peter Stegemann schrieb:
> Sammle ein paar Wochen Erfahrung mit Assembler

Ich sammle gerade Erfahrungen mit C :-) Erstmal eins verstehen - 
Assembler wäre natürlich noch besser.

von Dietmar Hersch (Gast)


Lesenswert?

Eine andere Sache noch, wo wir gerade dabei sind...

Ich habe einen float, der besteht aus 4 Bytes - wenn ich nun die vier 
einzelnen Bytes dieses Floats haben will, dann kann ich das doch auch 
über die Adresse machen, oder? Müsste doch dann gehen.

z.B.:
1
float wert = -35.7736
2
uint8_t buffer[4];
3
4
float_to_uint8 (wert, &buffer)
5
6
void float_to_uint8 (float floatwert, uint8_t *buffer)
7
{
8
  uint8_t i = 3;
9
10
  for (i = 0; i < 4; i++)
11
  {
12
    buffer[i] = (uint8_t) floatwert++;
13
  }
14
}


Stimmt das so in etwa, oder wie würde man das machen?

von Karl H. (kbuchegg)


Lesenswert?

Dietmar Hersch schrieb:
> Eine andere Sache noch, wo wir gerade dabei sind...
>
> Ich habe einen float, der besteht aus 4 Bytes - wenn ich nun die vier
> einzelnen Bytes dieses Floats haben will, dann kann ich das doch auch
> über die Adresse machen, oder?

Kann man, ja.


> float wert = -35.7736
> uint8_t buffer[4];
>
> float_to_uint8 (wert, &buffer)

Noch mal.
Arrays werden immer übergeben, indem die Startadresse des Arrays 
übergeben wird. Der & ist hier an dieser Stelle überflüssig, weil buffer 
ein Array ist!

> void float_to_uint8 (float floatwert, uint8_t *buffer)
> {
>   uint8_t i = 3;
>
>   for (i = 0; i < 4; i++)
>   {
>     buffer[i] = (uint8_t) floatwert++;
>   }
> }

Aber nicht so.
floatwert ist ja noch wie vor ein float Wert.
Durch Nennung des Variablennamens erhältst du den Wert in der Variablen 
... welcher entsprechend dem Datentyp ein float ist.

Du würdest dich sehr wundern, wenn in
1
   NettoPreis = SteuerSatz * BrottoPreis;

auf einmal völlig unmotiviert mit einer Speicheradresse gerechnet werden 
würde, anstatt mit den Inhalten von SteuerSatz bzw. BruttoPreis

> Stimmt das so in etwa, oder wie würde man das machen?

zb so
1
void float_to_uint8 (float floatwert, uint8_t *buffer)
2
{
3
  uint8_t i;
4
5
  for (i = 0; i < sizeof(floatwert); i++)
6
    buffer[i] = *((uint8_t*)&floatwert) + i );
7
}

oder so
1
void float_to_uint8 (float floatwert, uint8_t *buffer)
2
{
3
  uint8_t i;
4
  uint8_t *floatBytes = &floatwert;
5
6
  for (i = 0; i < sizeof(floatwert); i++)
7
    buffer[i] = floatBytes[i];
8
}

oder so
1
void float_to_uint8 (float floatwert, uint8_t *buffer)
2
{
3
  uint8_t i;
4
  uint8_t *floatBytes = &floatwert;
5
6
  for (i = 0; i < sizeof(floatwert); i++)
7
    buffer[i] = *floatBytes++;
8
}

oder so
1
void float_to_uint8 (float floatwert, uint8_t *buffer)
2
{
3
  memcpy( buffer, (uint8_t*)&floatwert, sizeof(floatwert) );
4
}

(Es lohnt sich, wenn man seinen Baukasten an vorhandenen Funktionen 
kennt. Spart so manche Mühe)

von Dietmar Hersch (Gast)


Lesenswert?

Oh je...

Karl heinz Buchegger schrieb:
> void float_to_uint8 (float floatwert, uint8_t *buffer)
> {
>   uint8_t i;
>
>   for (i = 0; i < sizeof(floatwert); i++)
>     buffer[i] = *((uint8_t*)&floatwert) + i );
> }

Ok, also ich versuchs mal (vielen Dank für die Geduld bis jetzt 
schonmal!!!):

Der "sizeof" liefert mir ja die Anzahl in Bytes zurück, richtig? Also 
ergibt sizeof "4" - Die Schleife wird also 4-mal durchlaufen.

buffer[i] = *((uint8_t*)&floatwert) + i);

Der erste *Stern sorgt hier dafür, dass das Resultatden INHALT 
wiedergibt.

Mit (uint8_t*) caste ich das Ergebnis ja auf ein unsigned Byte, der 
*Stern hier ist mir nicht klar. Eine öffnende Klammer fehlt, ich denke 
sie kommt dann einfach noch hinter den ersten Stern.

&floatwert bezeichnet ja die Adresse - das +i schiebt dann die Adresse 
weiter?

Ihr seht, ich hab schon Probleme, eine der drei Varianten direkt zu 
verstehen...und ihr schüttelt mal eben drei Stück einfach so raus :)

von Karl H. (kbuchegg)


Lesenswert?

Dietmar Hersch schrieb:

> Der "sizeof" liefert mir ja die Anzahl in Bytes zurück, richtig? Also
> ergibt sizeof "4" - Die Schleife wird also 4-mal durchlaufen.

richtig.
Da aber niemand garantiert dass ein float immer 4 Bytes hat, bin ich 
hier auf der sicheren Seite. Sollte das einmal anders sein, dann 
erledigt der Compiler die Anpassung, indem sizeof einen anderen Wert 
ergibt.

-> lass im Zweifelsfall immer den Compiler für dich arbeiten!

>
> buffer[i] = *((uint8_t*)&floatwert) + i);
>
> Der erste *Stern sorgt hier dafür, dass das Resultatden INHALT
> wiedergibt.

Du gehst das falsch an.

Fang beim Variablennamen an und arbeite dich von innen nach aussen durch

   floatwert   ist ein float
  &floatwert   ist die Adresse, wo dieser float abgespeichert ist.
               vulgo: dieser Ausdruck ergibt einen Pointer auf float

  (uint8_t*)&floatwert    aus diesem Pointer auf float wird ein
                          Pointer of uint8_t. Der Zahlenwert der Adresse
                          ist immer noch derselbe, aber der Datentyp
                          hat sich geändert.

  ((uint8_t*)&floatwert) + i   von dieser Adresse (auf uint8_t) gehst
                               dann i Stück uint8_t weiter im Speicher.

  *(((uint8_t*)&floatwert) + i)   und von der Adresse, an der wir dann
                                  sind, von dort wird gelesen.

                                  Gelesen wird ein uint8_t, weil ja der
                                  Pointer (+Offset) auch zu einem
                                  uint8_t* wurde.

von Karl H. (kbuchegg)


Lesenswert?

Dietmar Hersch schrieb:

> Eine öffnende Klammer fehlt, ich denke
> sie kommt dann einfach noch hinter den ersten Stern.
>


Uuups.
Kommt vom direkten Schreiben hier im Forum.
Ja da fehlt eine. Und ja, es muss

   *(((uint8_t*)&floatwert) + i)

heissen.

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.