Ja, ich dachte bisher ich habe Zeiger und Arrays verstanden.
Doch jetzt komm ich grad an einen Punkt wo ich an mir zweifle.
Ich habe ein lokales Array:
1
uint8_tmyArray[]={0x00,0x01,0x02,0x03,0x04,0x05};
Nun will ich eine Funktion schreiben die so ein Array entgegennimmt und
zwei Aufgaben hat:
1) irgendwas aus den Werten berechnen, das ist ja trivial
2) als Nebeneffekt: das Array inkrementieren, damit ein zweiter Call zu
dieser Funktion ein anderes Ergebnis liefert
1
voiddoSomething(uint8_t*ptr);
Eine Solche Funktion erfüllt Kriterium 1). Ich kann ihr mein Array
übergeben. Der Name des Arrays "decayed" dabei zu einem Zeiger aufs
erste Element, also zu
1
&(myArray[0])
. doSomething() kann also auf Array-Elemente zugreifen und damit was
berechnen.
Allerdings: eine solche Funktion kann das Array nicht "weiterzählen".
1
voiddoSomething(uint8_t*ptr)
2
{
3
...berechnung...
4
5
ptr++;
6
}
macht natürlich nicht das was ich will, ptr++ zählt ja nur die lokale
Kopie hoch, die nach dem return eh verloren ist.
Ich muss also meinen Zeiger-auf-Array by-reference übergeben, meine
Funktion müsste also so aussehen:
1
voiddoSomething(uint8_t*ptr[]);
Der Aufruf geschieht dann so:
1
doSomething(&myArray);
1
voiddoSomething(uint8_t*ptr)
2
{
3
...berechnung...
4
5
(*ptr)++;
6
}
So kann ich dann den Zeiger des Callers derart manipulieren dass er beim
nächsten Aufruf auf andere Daten operiert.
Soweit die Theorie:
Unter Win7_x64 hat das Ganze einen Absturz zufolge, ich denke mal unter
Linux hätt ich hier die Ausgabe: Segmentation Fault.
Wo liegt denn da mein Denkfehler?
Wie kann ich es erreichen dass eine Helferfunktion den lokalen Zeiger
des Callers inkrementiert?
myArray ist eine Konstante, die auf eine bestimmte Adresse im Speicher
zeigt, die kannst du gar nicht hoch zählen. Mich wundert, dass &myArray
geht; da Konstanten ja keinen Speicherplatz belegen können sie auch
keine Adresse haben. (Es kann aber sein, dass wegen der Pointer/Array
Äquivalenz in diesem Fall &myArray=myArray gilt, habe ich jetzt nicht
geprüft.)
Du brauchst so etwas:
1
uint8_t*pMyArray=myArray;
Den Pointer kannst Du hoch zählen und der hat auch eine Adresse.
PS: Woher weiß Deine Funktion, wie lang das Restfeld noch ist?
Michl schrieb:> Nun will ich eine Funktion schreiben die so ein Array entgegennimmt und> zwei Aufgaben hat:> 1) irgendwas aus den Werten berechnen, das ist ja trivial> 2) als Nebeneffekt: das Array inkrementieren, damit ein zweiter Call zu> dieser Funktion ein anderes Ergebnis liefert
"das Array inkrementieren" ergibt keinen Sinn. Ein Array kann man nicht
inkrementieren.
> Allerdings: eine solche Funktion kann das Array nicht> "weiterzählen".void doSomething(uint8_t *ptr)> {> ...berechnung...>> ptr++;> }
Ah, du möchtest also nicht ein Array, sondern einen Zeiger
inkrementieren.
> macht natürlich nicht das was ich will, ptr++ zählt ja nur die lokale> Kopie hoch, die nach dem return eh verloren ist.
Richtig.
> Ich muss also meinen Zeiger-auf-Array by-reference übergeben, meine> Funktion müsste also so aussehen:> void doSomething(uint8_t *ptr[]);
Auch richtig. Du kansnt auch schreiben:
1
voiddoSomething(uint8_t**ptr);
>> Der Aufruf geschieht dann so:> doSomething(&myArray);> Wo liegt denn da mein Denkfehler?
myArray ist ein Array. &myArray ist ein Zeiger auf ein Array. Du mußt
aber einen Zeiger auf einen Zeiger übergeben, nicht auf ein Array.
> Wie kann ich es erreichen dass eine Helferfunktion den lokalen Zeiger> des Callers inkrementiert?
Es würde schon mal helfen, wenn dieser lokale Zeiger überhaupt
existieren würde. ;-)
Sorry, hier ein komplettes Beispiel.
Was ich herausgefunden habe:
Nicht das Inkrementieren verursacht den Absturz, sondern die Zeile
local = *buf[0];
Aber wieso? Ich muss doch den Zeiger doppelt dereferenzieren, einmal
durch den *-Operator, einmal durch [].
Michl schrieb:
Definiere erst mal, was das hier
> 2) als Nebeneffekt: das Array inkrementieren
bedeuten soll.
Was soll 'das Array inkrementieren' für eine Operation sein? Was soll
die bewirken? Beschreib das doch mal näher (ja, ja ich weiß schon. Beim
nächsten Aufrf soll mit anderen Daten operiert werden. Aber wo sind denn
diese anderen Daten?)
Michl schrieb:> Nicht das Inkrementieren verursacht den Absturz, sondern die Zeile> local = *buf[0];> Aber wieso?
Du übergibst die Adresse von myArray. Wenn du die dereferenzierst,
kommst du auf das erste Element des Arrays. Jetzt dereferenzierst du
nochmal, wodurch dieses erste Element (und noch ein paar folgende) als
Adresse interpretiert wird, und dann versucht das Programm, von dieser
Adresse zu lesen, wobei natürlich dann Blödsinn rauskommt.
Nachtrag:
folgender Code tut genau das was ich will, sogar ohne Warnungen:
1
#include<stdint.h>
2
#include<stdio.h>
3
4
voiddoSomething(uint8_t*buf[])
5
{
6
uint8_tlocal;
7
8
local=*buf[0];
9
10
printf("%x\n",local);
11
12
(*buf)++;
13
}
14
15
16
voidhelp(uint8_t*buf)
17
{
18
doSomething(&buf);
19
doSomething(&buf);
20
}
21
22
23
intmain(void)
24
{
25
uint8_tmyArray[]={0x00,0x01,0x02,0x03,0x04,0x05};
26
27
help(myArray);
28
29
return0;
30
}
Durch den Umweg über die help-Funktion geht in doSomething() die Info
verloren dass da irgendwo ein Array dahinter liegt.
Ich denke mal das ist das Problem, da man, wie Rolf Magnus und KH
Buchegger anmerkten, ein Array nicht inkrementieren kann. Der Umweg über
help() ist ja letzendlich gleichwertig mit der von Rolf Magnus
vorgeschlagenen Lösung, den lokalen Pointer anzulegen.
Karl Heinz schrieb:> Was soll 'das Array inkrementieren' für eine Operation sein? Was soll> die bewirken? Beschreib das doch mal näher (ja, ja ich weiß schon. Beim> nächsten Aufrf soll mit anderen Daten operiert werden. Aber wo sind denn> diese anderen Daten?)
Das dient dazu, aus einem Bytestrom einige Bytes herauszulesen. Immer
Häppchenweise, und diese zu verarbeiten.
Michl schrieb:> Sorry, hier ein komplettes Beispiel.
Dieses Beispiel ergibt keinen Sinn.
Das hier
1
voiddoSomething(uint8_t*buf[])
besagt, dass buf ein Array von Pointer ist, wobei jeder Pointer auf
(mindestens) einen uint8_t zeigt.
Hier hast du also vereinbart, dass buf diese Gestalt hat
1
buf
2
+------+ +---+---+---+---+---+
3
| o----------------------->| 3 | 8 | 5 | 2 | 4 |
4
+------+ +---+---+---+---+---+
5
| o------------+
6
+------+ | +---+---+
7
| o-------+ +--->| 2 | 8 |
8
+------+ | +---+---+
9
| o-----+ |
10
+------+ | |
11
| +-----> ....
12
|
13
....
Nur: Deine Daten sehen nicht so aus!
Das hier
1
uint8_tmyArray[]={0x00,0x01,0x02,0x03,0x04,0x05};
ist etwas völlig anderes und auch wenn du mittels eines Adressoperators
erst mal alles ruhig gestellt hast, ändert das nichts an der Tatsache,
dass deine Daten nicht den Aufbau haben, den die Funktion voraussetzt.
Du kannst eine Kuh weiß anstreichen, aber das macht sie nicht zum
Schimmel.
Michl schrieb:> Karl Heinz schrieb:>> Was soll 'das Array inkrementieren' für eine Operation sein? Was soll>> die bewirken? Beschreib das doch mal näher (ja, ja ich weiß schon. Beim>> nächsten Aufrf soll mit anderen Daten operiert werden. Aber wo sind denn>> diese anderen Daten?)>> Das dient dazu, aus einem Bytestrom einige Bytes herauszulesen. Immer> Häppchenweise, und diese zu verarbeiten.
Schön.
Aber wo sind diese Daten in deinem Programm.
Wenn du sagst, du willst ein anderes Array benutzen (und das tust du mit
deinem Code), dann muss es auch ein anderes Array geben. Wo ist dieses?
Michl schrieb:> Was ich herausgefunden habe:> Nicht das Inkrementieren verursacht den Absturz, sondern die Zeile> local = *buf[0];> Aber wieso? Ich muss doch den Zeiger doppelt dereferenzieren, einmal> durch den *-Operator, einmal durch [].
Der []-Operator hat eine höhere Priorität, als der *-Operator. Folglich
ist
1
uint8_t*buf[]
nicht ein pointer auf ein Array, sondern ein Array von Pointern (auf
uint8_t). Wenn, dann muss es so heißen:
Naja ich will eigentlich kein anderes Array verwenden.
myArray enthält den Datenstrom, den ich auswerten will.
Immer häppchenweise. Beim ersten mal eben das 0x00 in meinem Beipsiel,
danach 0x01, 0x02, usw.
A. H. schrieb:> Michl schrieb:>> Was ich herausgefunden habe:>> Nicht das Inkrementieren verursacht den Absturz, sondern die Zeile>> local = *buf[0];>> Aber wieso? Ich muss doch den Zeiger doppelt dereferenzieren, einmal>> durch den *-Operator, einmal durch [].>> Der []-Operator hat eine höhere Priorität, als der *-Operator. Folglich> ist
Hier kommt eher die Rechts-Links Regel zur Anwendung
http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html
Michl schrieb:> Naja ich will eigentlich kein anderes Array verwenden.> myArray enthält den Datenstrom, den ich auswerten will.>> Immer häppchenweise. Beim ersten mal eben das 0x00 in meinem Beipsiel,> danach 0x01, 0x02, usw.
ALso willst du sowas:
1
voiddoSomething(uint8_t**buf)
2
{
3
uint8_tlocal;
4
5
local=*buf[0];
6
7
printf("%x\n",local);
8
9
(*buf)++;
10
}
11
12
intmain(void)
13
{
14
uint8_tmyArray[]={0x00,0x01,0x02,0x03,0x04,0x05};
15
uint8_t*nextElem;
16
17
nextElem=myArray;
18
19
doSomething(&nextElem);
20
doSomething(&nextElem);
21
22
return0;
23
}
willkommen in der 2-Stern Programmierung, die sich völlig analog aus der
Situation ableitet, wie eine Funktion vorgehen muss, damit sie eine
Variable beim Aufrufer verändern kann
1
voidfoo(int*a)
2
{
3
*a=5;
4
}
5
6
intmain()
7
{
8
intc;
9
10
foo(&c);
11
}
der Aufrufer übergibt einen Pointer auf die Variable und die Funktion
kann über diesen Pointer die Variable des Aufrufers ändern. Das geht für
alle Datentypen T
1
voidfoo(T*a)
2
{
3
*a=fürTzulässigerWert;
4
}
5
6
intmain()
7
{
8
Tc;
9
10
foo(&c);
11
}
In deinem Fall ist T dann eben der Datentyp 'Pointer to uint8_t', weil
du eine Variable brauchst, in der du fest hältst, wo in deinem Array das
nächste zu bearbeitende Byte zu finden ist.
Also ersetzte T durch uint8_t und du erhältst
1
voidfoo(uint8_t**a)
2
{
3
*a=....
4
}
5
6
intmain()
7
{
8
uint8_t*c;
9
10
foo(&c);
11
}
dein c ist aber nicht irgendein c, sondern enthält eine Adresse, die in
ein Array zeigt
1
voidfoo(uint8_t**a)
2
{
3
// a ist die Adresse von c
4
// *a ist daher der INhalt von c. Der INhalt von c, das ist aber nichts
5
// anderes als die Adresse des nächsten zu verarbeitenden Zeichens
6
// daher ist **a dieses Zeichen selber
7
8
machwasmit**a
9
10
// und vermerken, dass dieses Zeichen somit bearbeitet wurde. Das c
11
// vom Aufrufer soll auf den nächsten Speicherplatz im Array zeigen
Michl schrieb:> sogar ohne Warnungen
Aha! Ich hatte mich schon gewundert, weil der Code davor eigentlich über
die Typ-Unstimmigkeit hätte warnen müssen. Hat er also offenbar auch,
aber du hast die Warnung für nicht wichtig genug befunden, um sie hier
auch mit zu posten.
Michl schrieb:> Der Umweg über> help() ist ja letzendlich gleichwertig mit der von Rolf Magnus> vorgeschlagenen Lösung, den lokalen Pointer anzulegen.
Ja, richtig. In dem Fall ist halt der Parameter buf der lokale Pointer.
Karl Heinz schrieb:> Das hier> void doSomething(uint8_t *buf[])> besagt, dass buf ein Array von Pointer ist, wobei jeder Pointer auf> (mindestens) einen uint8_t zeigt.
Nein. buf ist ein Zeiger auf einen Zeiger.
Karl Heinz schrieb:> Aber wo sind diese Daten in deinem Programm.> Wenn du sagst, du willst ein anderes Array benutzen (und das tust du mit> deinem Code), dann muss es auch ein anderes Array geben. Wo ist dieses?
Er will nicht ein anderes Array benutzen, sondern mittels eines Pointers
durch ein Array iterieren.
A. H. schrieb:> Folglich ist> uint8_t *buf[]> nicht ein pointer auf ein Array, sondern ein Array von Pointern (auf> uint8_t).
buf ist ein Funktionsparameter. Da haben die Klammern nichts mit Arrays
zu tun. buf ist schlicht und ergreifend ein Zeiger auf einen Zeiger!
uint8_t *buf[] ist als Parameter zu 100% äquivalent zu uint8_t ** buf.
Wozu man vielleicht noch ergänzend folgendes erwähnen kann.
Der Name eines Vektors ist nicht gleichbedeutend mit einer Variablen,
die einen Zeiger enthält. Demzufolge kann man unter diesem Namen auch
keinen Zeiger manipulieren.
Vielmehr ist der Name eines Vektors synonym zu einem Zeiger auf diesen
Vektor. D.h. bei Verwendung des Vektornamens im Code wird an seiner
Stelle, während der Compilation resp. Linken, ein Zeiger auf das erste
Element verwendet. Hingegen existiert der Zeiger zur Laufzeit in keiner
Weise als "Ding", das im Quellcode irgendwie nennbar wäre.
Rolf Magnus schrieb:>> void doSomething(uint8_t *buf[])>> besagt, dass buf ein Array von Pointer ist, wobei jeder Pointer auf>> (mindestens) einen uint8_t zeigt.>> Nein. buf ist ein Zeiger auf einen Zeiger.
Wenn wir uns nur auf den Datentyp konzentrieren, dann schon.
Allerdings ist das hier in einer Situation, in der Arrays automatisch zu
Zeigern decayen. Somit haben wir beide recht.
Letzten Endes geht es darum, dass in C die alternative Schreibweisen
1
voidfoo(T*arg)
und
1
voidfoo(Targ[])
technisch gesehen aufs gleiche rauslaufen, auch wenn sie etwas anders
suggerieren.
Karl Heinz schrieb:> A. H. schrieb:>> Michl schrieb:>>> Was ich herausgefunden habe:>>> Nicht das Inkrementieren verursacht den Absturz, sondern die Zeile>>> local = *buf[0];>>> Aber wieso? Ich muss doch den Zeiger doppelt dereferenzieren, einmal>>> durch den *-Operator, einmal durch [].>>>> Der []-Operator hat eine höhere Priorität, als der *-Operator. Folglich>> ist>> Hier kommt eher die Rechts-Links Regel zur Anwendung>> http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html
Naja, diese Regel würde ich eher als eine Gedankenstütze für
Programmierer verstehen, denn der Compiler richtet sich bei der Analyse
des Codes und dem Aufbau des Syntaxbaumes definitiv nach den
Präzedenzregeln: Hat er den Identifikator erkannt (in diesem Fall buf)
muss er entscheiden ob er den indizieren oder dereferenzieren soll. Da
die Indizierung die höhere Präzedenz hat, wird er zunächst annehmen,
dass es sich um ein Array handelt, dessen Element dann dereferenziert
werden soll, also ein Pointer sein muss.
> was aber wegen der Null in den eckigen Klammern (und nur deswegen!) auf> das gleiche raus läuft wie das, was du geschrieben hast.
Danke.
Den hab ich beim kopieren übersehen.
tictactoe schrieb:> Genau genommen> local = (*buf)[0];
Da hier ja eh keine wirkliche Array-Indizierung benötigt wird, sondern
einfach nur einen Zeiger auf einen Zeiger zweimal dereferenziert werden
soll, würde ich gleich schreiben:
1
local=**buf;
Karl Heinz schrieb:> Wenn wir uns nur auf den Datentyp konzentrieren, dann schon.> Allerdings ist das hier in einer Situation, in der Arrays automatisch zu> Zeigern decayen. Somit haben wir beide recht.
Naja, du meintest vorhin:
Karl Heinz schrieb:> ist etwas völlig anderes und auch wenn du mittels eines Adressoperators> erst mal alles ruhig gestellt hast, ändert das nichts an der Tatsache,> dass deine Daten nicht den Aufbau haben, den die Funktion voraussetzt.
Die Daten haben aber durchaus den Aufbau, den die Funktion voraussetzt,
auch wenn das auf den ersten Blick nicht so aussieht.
> Du kannst eine Kuh weiß anstreichen, aber das macht sie nicht zum> Schimmel.
Tatsächlich ist es hier so, daß es bereits ein Schimmel ist, der nur
durch die ulkige Syntax von C wie eine Kuh aussieht. ;-)
Karl Heinz schrieb:> technisch gesehen aufs gleiche rauslaufen, auch wenn sie etwas anders> suggerieren.
Ebenth!
Rolf Magnus schrieb:> Die Daten haben aber durchaus den Aufbau, den die Funktion voraussetzt,> auch wenn das auf den ersten Blick nicht so aussieht.
In seinem Originalbeispiel nicht.
Da fehlte die Zwischenebene einer Pointer-Variablen
@TO
>> Aber: ändere ich die Zeile> local = *buf[0];> in doSomething() zu>> local = *buf[1];
Mittels
1
uint8_tmyArray[]={0x00,0x01,0x02,0x03,0x04,0x05};
2
uint8_t*nextElem;
3
4
nextElem=myArray;
hast du jetzt das hier aufgebaut
1
nextElem
2
+-------+ +------+
3
| o----------------------->| 0x00 |
4
+-------+ +------+
5
| 0x01 |
6
+------+
7
| 0x02 |
8
.....
Durch die Übergabe an die Funktion hast du eine weitere Ebene eingebaut
1
buff
2
+-------+
3
| o-----------+
4
+-------+ |
5
|
6
v
7
+--------+ +------+
8
nextElem | o------------------>| 0x00 |
9
+--------+ +------+
10
| 0x01 |
11
+------+
12
| 0x02 |
13
....
was besagt nun
1
*buf[0]
es besagt: verfolge den Zeiger buf und dort wo er hin führt, nimm das
Array Element 0 (das ist jetzt etwas salopp formuliert, aber darauf
läuft es hinaus).
Nun, wo zeigt denn buf hin?
buf zeigt auf nextElemen (auch wenn buf davon nichts weiß). nextElem ist
kein Array, aber durch den Array-Pointer Dualismus ist das bei [0] ok.
Da passiert nichts.
Ganz anders aber bei
1
*buf[1]
jetzt willst du durch das [1] hier
1
buff
2
+-------+
3
| o-----------+
4
+-------+ |
5
|
6
v
7
+--------+ +------+
8
nextElem | o------------------>| 0x00 |
9
+--------+ +------+
10
###### | 0x01 |
11
+------+
12
| 0x02 |
13
....
zugreifen und dir von hier den Pointer holen, mit dem du dann auf die
eigentlichen Daten weiter zugreifst (was dann der * in *buf[1] gemacht
hätte).
Nur sieht man in der Grafik auch: An der Stelle der ####### ist gar kein
Zeiger! Du greifst auf irgendeinen Speicher zu, und was immer auch dort
im Speicher steht, es wird als die Adresse aufgefasst, mit der dann
weiter dereferenziert wird.
Und das geht dann in die Hose.
Und ich schliesse mich Rolf an.
Da das, worauf buf zeigt, in erster Linie gar kein Array ist, würde ich
das auch nicht mit Array Syntax schreiben, sondern als
1
**buf
schreiben. Die Array Indizierung, die zwar bei [0] das identische
Verhalten hervorruft, führt einen hier in die Irre: sie gaukelt etwas
vor, was nicht da ist: Das worauf buf zeigt ist kein Array (auch wenn es
eines sein könnte und sich an der Syntax dadurch nichts ändern würde)
Rolf Magnus schrieb:> A. H. schrieb:>> Folglich ist>> uint8_t *buf[]>> nicht ein pointer auf ein Array, sondern ein Array von Pointern (auf>> uint8_t).>> buf ist ein Funktionsparameter. Da haben die Klammern nichts mit Arrays> zu tun. buf ist schlicht und ergreifend ein Zeiger auf einen Zeiger!> uint8_t *buf[] ist als Parameter zu 100% äquivalent zu uint8_t ** buf.
Einigen wir uns auf die Formulierung: Der Compiler wandelt implizit eine
Array von Pointern in ein Pointer auf Pointer? Syntaktisch bleibt der
Ausdruck nämlich ein Array von Pointern, ungeachtet der semantischen
Kapriolen, die der Compiler dann damit treibt (und die sicherlich zum
weniger glücklichen historischen Erbe von C gehören). Technisch gesehen
ist Deine Aussage natürlich richtig, was den Lerneffekt betrifft hielte
ich in diesem Fall die Unterscheidung zwischen Pointer auf Array und
Array von Pointern aber für wichtiger, die spätestens dann relevant
wird, wenn die Indexklammern nicht mehr leer (bzw. != 0) sind und die
offenbar nicht ganz einfach ist, wie der immer noch fehlerhafte
Quelltext und die wiederholten Fragen des TO deutlich zeigen. Man möge
mir die kleine technische Ungenauigkeit an dieser Stelle daher verzeihen
:-)
A. H. schrieb:> Syntaktisch bleibt der Ausdruck nämlich ein Array von Pointern,> ungeachtet der semantischen Kapriolen, die der Compiler dann damit treibt> (und die sicherlich zum weniger glücklichen historischen Erbe von C> gehören).
Es ist eines der Dinge in C, die ursprünglich die Sprache intuitiver
hätten machen sollen, aber leider (zumindest für jemanden, der die
Sprache wirklich verstehen will) genau das Gegenteil tun. Gut - kann man
heute nicht mehr ändern.
Um es nochmal auf die schöne Analogie von Karl-Heinz zu beziehen: Ich
will eine Kuh und bekomme stattdessen einen Schimmel, der so angemalt
ist, daß er aussieht wie eine Kuh. Spätestens beim melken fällt einem
dann auf, daß da was nicht stimmt.
A. H. schrieb:> Man möge mir die kleine technische Ungenauigkeit an dieser Stelle daher> verzeihen :-)
Das ist vielleicht ein anderer pädagogischer Ansatz. Ich erkläre lieber
einem Einsteiger die Dinge korrekt, auch wenn's das am Anfang
komplizierter macht. Es mag aber auch Vorteile haben, manchmal nicht so
genau auf die Details zu achten, um den Einstieg zu erleichtern.