Servus ihr Lieben, ich habe seit paar Tagen mit der C Programmierung begonnen und ich habe aktuell ein Problem welches ich noch nicht Lösen konnte. Ich stehe vor folgender Problemstellung: Ich möchte einer Funktioen einen pointer übergeben und diesen einen schritt weiterlaufen lassen, hierzu der Quellcode: void pointerVersuch(int *zahl){ zahl++; } int main(){ int array[3] = {0,1,2}; int *zahl = array; while(1U){ pointerVersuch(zahl); } return 0; } Der Befehl "zahl++;" lässt diesen auch weiterspringen, allderings wird der pointer wieder auf die alte Adresse gesetzt sobald die Funktion beendet ist (Im Debugger geschiet dies in Zeile der, also bei der geschlossenen Klammer des Funktionsrumpfes). Ich verstehe nicht ganz wieso. Ich sollte doch mit der Funktion pointer inkrementieren können, oder etwa nicht ? Wo ist mein Denkfehler ? V Vielen Dank.
:
Verschoben durch Moderator
zahl++ inkrementiert den Pointer selbst, d.h. er zeigt danach auf das nächste Element im Array. Die Elemente bleiben unverändert. Wenn du (*zahl)++ schreibst, wird das Ziel des Pointers, d.h. das 0. Element im Array, verändert. Der Pointer zeigt danach immer noch auf dieses Element.
Wenn du den Pointer selbst verändern willst (und nicht die Daten auf die er zeigt) musst du einen Pointer auf diesen Pointer übergeben. void pointerVersuch(int **zahl){ } pointerVersuch(&zahl);
:
Bearbeitet durch User
Danke für die sehr schnelle Antwort. Ja ich weiß das der Wert sich so nicht ändert. Ich möchte nur, dass der ponter auf die nächste Adresse zeigt und diese Adresse beibehält wenn ich die Funktion verlasse. Ich verstehe nicht wieso der pointer nach verlassen der Funktion auf die Adresse vor "zahl++;" zurücksrpingt.
Marco D. schrieb: > Ich verstehe nicht wieso der pointer nach verlassen der Funktion auf > die Adresse vor "zahl++;" zurücksrpingt. Weil der Funktion eine Kopie des Pointers übergeben wird und die Funktion nur die Kopie verändert. Das ist bei allen Funktionsparametern so, auch bei int-Werten:
1 | void funktion(int wert) |
2 | {
|
3 | wert++; |
4 | }
|
5 | |
6 | |
7 | // ... irgendwo
|
8 | |
9 | int bla = 5; |
10 | |
11 | printf("%d\n", bla); |
12 | |
13 | funktion(bla); |
14 | |
15 | printf("%d\n", bla); |
Wird's klarer?
Wenn du ein Objekt modifizieren willst, musst du einen Pointer oder eine Referenz auf das Objekt übergeben. Dabei spielt es keine Rolle, ob das Objekt selbst bereits ein Pointer ist oder nicht.
Marco D. schrieb: > Danke für die sehr schnelle Antwort. > > Ja ich weiß das der Wert sich so nicht ändert. Ich möchte nur, dass der > ponter auf die nächste Adresse zeigt und diese Adresse beibehält wenn > ich die Funktion verlasse. Ich verstehe nicht wieso der pointer nach > verlassen der Funktion auf die Adresse vor "zahl++;" zurücksrpingt. Weil der Pointer selbst "by value" übergeben wird. Der Pointer selbst wird kopiert. Die Funktion bekommt nur die Kopie. Die kann sie ändern so viel sie will. Das Original bleibt unverändert. Will man das Original verändern, so muss man einen Pointer darauf übergeben. In deinem Fall also, eine Pointer auf den Pointer.
Marco D. schrieb: > ch möchte nur, dass der > ponter auf die nächste Adresse zeigt und diese Adresse beibehält wenn > ich die Funktion verlasse. Achso. Falsch gelesen. Marco D. schrieb: > Ich verstehe nicht wieso der pointer nach > verlassen der Funktion auf die Adresse vor "zahl++;" zurücksrpingt. Ein Pointer ist auch eine Art von Wert. Indem du den Pointer an die Funktion übergibst, wird dieser Pointer kopiert. In der Funktion inkrementierst du diese Kopie des Pointers. Nach Zurückkehren dieser Funktion verschwindet diese Kopie wieder. Da du ja aber nicht die Kopie ändern möchtest, sondern das Original, musst du wie cyblord gezeigt hat, einen Pointer auf den Pointer übergeben. Das Inkrementieren geht dann mit "(*zahl)++" - denn du willst ja das Ziel des Doppelpointers, nämlich den Original-Pointer, inkrementieren. Mit "(**zahl)++" könntest du auch das Array-Element inkrementieren.
Wow so schnell so viel Hilfe. Ja das macht absolut Sinn. Es wird bei Aufruf eine Kopie erstellt die nur während des Aufrufs besteht. OK vielen Dank !
Du hast zwei Möglichkeiten, den Wert aus einer Funktion heraus zu bekommen. Als Rückgabewert der Funktion
1 | int * pointerVersuch(int *zahl){ |
2 | zahl++; |
3 | |
4 | return zahl; |
5 | }
|
6 | |
7 | int main(){ |
8 | |
9 | int array[3] = {0,1,2}; |
10 | int *zahl = array; |
11 | |
12 | while(1U){ |
13 | zahl = pointerVersuch(zahl); |
14 | |
15 | }
|
16 | |
17 | return 0; |
18 | }
|
oder, wie schon gesagt, mit Übergabe der Adresse des Objektes.
Ich hab es anscheinend immer noch nicht ganz verstanden. Hier mein Beispielcode um en nachzuvollziehen: void pointertraining1(int array[]){ int *pointer = array; for(int i = 0;i<3;i++){ *pointer = 1; pointer++; } } void pointertraining2(int array[]){ for(int i = 0;i<3;i++){ array[i] = 2; } } int main(){ int zahlen[3] = {0,0,0}; pointertraining1(zahlen); pointertraining2(zahlen); } Wenn ich euch richtig verstanden habe, kann ich die Daten (den array in dem Fall) nur ändern, indem ich diese mittels pointer manipuliere, weil der pointer nicht auf die Kopie, sondern auf das Original zugreift. Im zweiten Fall ändere ich nur die Werte der Arraykopie die ich aufrufe und sobald die Funktion schließt, sollte array die vorherigen Werte wieder annehmen. Mit beiden Funktionen kann ich aber in array schreiben und diese bleiben nach Beendigung der Funktionen bestehen. Was ist der Unterschied zu dem Beispiel von Rufus T.F. ?
Arrays werden an Funktionen genauso übergeben wie ein Pointer auf das erste Element des Arrays. Das bedeutet, daß das Array nicht kopiert wird.
1 | void pointertraining2(int array[]) |
ist also exakt identisch zu
1 | void pointertraining2(int *array) |
Marco D. schrieb: > Im zweiten Fall ändere ich nur die Werte der Arraykopie Da gibt es nirgendwo eine Kopie des Array. Um Arrays zu kopieren verwendet man memcpy() oder ähnlich. In C laufen Arrayzugriffe immer über Pointer, wenn auch heimlich intern. Ob du dieses schreibst: > array[i] = 2; oder: > i[array] = 2; oder: > *(array+i) = 2; Das ist völlig egal. Es wird immer der gleiche Code generiert. Es zeigt Äquivalenz von Zeiger und Array Zugriffen
Arduino Fanboy D. schrieb: > In C laufen Arrayzugriffe immer über Pointer, wenn auch heimlich intern. Arduino Fanboy D. schrieb: > Das ist völlig egal. > Es wird immer der gleiche Code generiert. Das ist totaler Blödsinn! Optimierter Code kann gleich aussehen, aber pointer und array sind grundverschiedene Dinge. Ein pointer hat eine eigene Speicherstelle, in der die Adresse der Daten steckt, auf die der pointer zeigt. Ein array hat diese Speicherstelle nicht! @Fanboy: Bevor du flamess, lies hier: http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/012_c_zeiger_007.htm
Marco D. schrieb: > int *zahl Es wird anschaulicher und leichter zu lesen wenn Du den * an den Typbezeichner hängst und nicht an den Variablembezeichner: int* zahl Man kann nämlich argumentieren daß die Eigenschaft ein Pointer zu sein Teil des Typs ist und daher zu diesem gehört. Du hast eine Variable "zahl" und diese ist vom Typ "Zeiger auf int". Das Sternchen ist also ganz klar ein Bestandteil des Typs, es ist besser das auch durch die Schreibweise kenntlich zu machen, Typ und Variablenname durch ein Leerzeichen zu trennen und das Pointersternchen direkt an den Typnamen anzukleben wo es hingehört. das solltest Du Dir von Anfang an angewöhnen. Dem Compiler ist es egal, er sieht diese Leerzeichen überhaupt nicht aber die Lesbarkeit und Verständlichkeit steigert es enorm. Also int* zahl // "zahl" ist ein Pointer auf int int *zahl // wenn man den pointer "zahl" dereferenzieren // würde dann wäre das ein int also beides mal das exakt selbe in jeder Hinsicht, nur in der zweiten Form hoffnungslos verklausuliert gedacht und hingeschrieben.
:
Bearbeitet durch User
Klugscheisservernichter schrieb: > @Fanboy: Bevor du flamess, lies hier: > http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/012_c_zeiger_007.htm Gerade das Buch, ist nun wirklich keine gute Quelle.... In Sachen Zeiger, hat der Autor, bzw. sein Buch, einige Schwächen.
Klugscheisservernichter schrieb: > Das ist totaler Blödsinn! Optimierter Code kann gleich aussehen, aber > pointer und array sind grundverschiedene Dinge. Oh je, da hat aber jemand C so überhaupt nicht verstanden. Arduino Fanboy D. schrieb: > Klugscheisservernichter schrieb: >> @Fanboy: Bevor du flamess, lies hier: >> http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/012_c_zeiger_007.htm > Gerade das Buch, ist nun wirklich keine gute Quelle.... > In Sachen Zeiger, hat der Autor, bzw. sein Buch, einige Schwächen. Sehe ich ganz genauso. Wie wäre es stattdessen mit "6.5.2.1 Array Subscripting" aus dem C99 Standard: > A postfix expression followed by an expression in square brackets [] is a > subscripted designation of an element of an array object. The definition of > the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). > Because of the conversion rules that apply to the binary + operator, if E1 > is an array object (equivalently, a pointer to the initial element of an array > object) and E2 is an integer, E1[E2] designates the E2-th element of E1 > (counting from zero).
:
Bearbeitet durch User
Bernd K. schrieb: > int* zahl // "zahl" ist ein Pointer auf int Jetzt deklariere einmal zwei Variablen per Komma. int a, b; Und jetzt du mit Pointer.
> A postfix expression......
Zeichne dir eine momory map und du wirst den Unterschied zwischen array
und Pointer verstehen.
Klugscheisservernichter schrieb: > Zeichne dir eine momory map und du wirst den Unterschied zwischen array > und Pointer verstehen. Ich brauch mir überhaupt nichts zeichnen, weil ich im Gegensatz zu dir verstanden habe, dass es keinen Unterschied gibt. Hier hast du ein Programm zum rumspielen. Sogar mit durch Komma getrennten Variablen (was ich bei Pointern allgemein für keine gute Idee halte, weil zu fehleranfällig).
1 | #include <stdio.h> |
2 | #include <stdint.h> |
3 | |
4 | int main(int argc, char const *argv[]) |
5 | {
|
6 | uint32_t array[3] = {123, 456, 789}; |
7 | |
8 | |
9 | uint32_t* a0; |
10 | uint32_t* a1; |
11 | uint32_t* a2; |
12 | |
13 | uint32_t* p0,* p1; |
14 | // uint32_t* p1;
|
15 | uint32_t* p2; |
16 | |
17 | a0 = &array[0]; |
18 | a1 = &array[1]; |
19 | a2 = &array[2]; |
20 | |
21 | p0 = array+0; |
22 | p1 = array+1; |
23 | p2 = array+2; |
24 | |
25 | printf("a[0] hat die Adresse %x, a[1] hat die Adresse %x und a[2] hat die Adresse %x\n", a0, a1, a2); |
26 | printf("p0 zeigt auf Adresse %x, p1 zeigt auf Adresse %x und p2 zeigt auf Adresse %x\n", p0, p1, p2); |
27 | |
28 | return 0; |
29 | }
|
Ausgabe:
1 | $ ./bin/helloworld |
2 | a[0] hat die Adresse f6e1fc4, a[1] hat die Adresse f6e1fc8 und a[2] hat die Adresse f6e1fcc |
3 | p0 zeigt auf Adresse f6e1fc4, p1 zeigt auf Adresse f6e1fc8 und p2 zeigt auf Adresse f6e1fcc |
Christopher J. schrieb: > Ausgabe:$ ./bin/helloworld > a[0] hat die Adresse f6e1fc4, a[1] hat die Adresse f6e1fc8 und a[2] hat > die Adresse f6e1fcc > p0 zeigt auf Adresse f6e1fc4, p1 zeigt auf Adresse f6e1fc8 und p2 zeigt > auf Adresse f6e1fcc Dann Spiel einmal mit deinem Programm und gib die Adresse von p0 aus (nicht worauf gezeigt wird). Dann verstehst auch du es.
Klugscheisservernichter schrieb: > Dann Spiel einmal mit deinem Programm und gib die Adresse von p0 aus > (nicht worauf gezeigt wird). Dann verstehst auch du es. Du willst es offensichtlich nicht verstehen, deshalb geb ich hier an der Stelle auf aber vielleicht hat es ja wenigstens dem TO etwas zum Verständnis geholfen.
Klugscheisservernichter schrieb: > Jetzt deklariere einmal zwei Variablen per Komma. > int a, b; > > Und jetzt du mit Pointer. Da verzichte ich lieber auf das Komma und schreib das untereinander. Oder wenn mehrere Variablen absichtlich ein einen bestimmten Typ haben sollen und dieser Typ irgendeine bestimmte Bedeutung hat der über den lokalen Kontext hinausgeht mach ich auch mal gerne ein typedef. Ich will keinen Preis dafür gewinnen möglichst viel auf eine Zeile gequetscht zu haben sondern dafür daß es möglichst übersichtlich aussieht, den Preis einer zusätzlichen Zeile bezahle ich in diesem Falle gerne. Ich hab damals noch auf Pascal gelernt und um dann den Umstellungsschock auf C so weit wie möglich abzufedern hab ich einige Gepflogenheiten mitgenommen, unter anderem die daß man allein mit den Typdeklarationen schon einiges über seine Abstraktion des Problems ausdrücken und dokumentieren kann und auch wenn vieles davon dem C-Compiler leider vollkommen egal ist dient es doch dem Menschen als Anhaltspunkt fürs Verständnis. In Pascal hätt ich übrigens schreiben können: var a, b: ^integer; Denn da ist es wirklich Teil der Typsyntax und nicht nur eine halbgare Zweitverwendung des Dereferenzierungsoperators, in C ist es eher schieres Glück daß man es wenigstens in den meisten Fällen ebenfalls so hinschreiben kann als wäre es das wonach es schon aus Symmetriegründen eigentlich aussehen müsste, also mach ich es auch so.
Klugscheisservernichter schrieb: > Das ist totaler Blödsinn! Optimierter Code kann gleich aussehen, aber > pointer und array sind grundverschiedene Dinge. Für Arrays und Pointer gelten völlig anders arbeitenden Speicherverwaltungen. In der Verwendung in Ausdrücken ist ein Array jedoch meist ein Pointer auf das erste Element in Form einer Adress-Konstante. Wobei eine Konstante weder änderbar ist, noch selbst wiederum eine Adresse besitzt. Der Ausdruck a[i] ist in C als *(a+i) definiert, egal ob a als Array oder als Pointer definiert wurde. In Ausdrücken Unterschiedlich behandelt werden Arrays und Pointer m.W. nur in sizeof. Weshalb C in Parameter-Deklarationen sowas wie f(int a[]) überhaupt zugelassen hat, habe ich nie verstanden.
:
Bearbeitet durch User
Bernd K. schrieb: > Es wird anschaulicher und leichter zu lesen wenn Du den * an den > Typbezeichner hängst und nicht an den Variablembezeichner: > > int* zahl > > Man kann nämlich argumentieren daß die Eigenschaft ein Pointer zu sein > Teil des Typs ist und daher zu diesem gehört. So könnte man vielleicht argumentieren, wenn man gleichzeitig das Deklarieren von mehreren Variablen per Komma verbietet (was auch eine gute Idee wäre). Sonst könnte man leicht auf die Idee kommen, dass hier zwei Pointer deklariert werden:
1 | uint32_t* a, b; |
Hier ist aber nur a ein "pointer to unsigned integer". Die Variable b ist vom Typ "unsigned integer", weil das Sternchen eben nicht Bestandteil des Typs ist.
Die Syntax von Deklaration in C ist ein typischer Fall von gut gemeint, aber schlecht gemacht. War eine nette Idee, sie wie die Verwendung aussehen zu lassen, ist aber zu einer unrettbaren Katastrophe geworden. Das muss man nicht mögen, man muss nur damit leben lernen.
schade das der gcc da nicht warnt, der ist ja schon sehr pedantisch was mögliche Fehler angeht. Aber die Verwendung des Wertes antelle des Pointers wird in den meisten Fällen zum Fehler führen sodass man eine falsche Deklaration schnell findet.
Peter S. schrieb: > uint32_t* a, b; > Hier ist aber nur a ein "pointer to unsigned integer" Wenn man Glück hat haut es einem Der Compiler kurz danach um die Ohren sobald man versucht den vermeintlichen Pointer auch als solchen zu benutzen. Aber ja, das ist nur wieder eine weitere häßliche Unvollkommenheit dieser Sprache die sich einreiht in eine lange Liste weiterer Unvollkommenheiten. Dennoch kann es gelingen auch in C sauberen Code zu schreiben wenn man etwas Disziplin an den Tag legt, von manchen Konstrukten einen selbst auferlegten Abstand nimmt. Keiner soll behaupten C sei eine einfache Sprache, man kann sich bei undurchsichtigem Bodennebel schnell in den eigenen Fuß schießen wenn man sich keine Konventionen angewöhnt wie zum Beispiel Fuß immer zurückziehen bevor man abdrückt, niemals auf zwei Füße gleichzeitig schießen, im Zweifel einen halben Meter zurück gehen.
:
Bearbeitet durch User
Infrequently Asked Questions in comp.lang.c 2.8: Practically speaking, what is the difference between arrays and pointers? About the difference between alcohol and marijuana; they have different characteristics, and that's not a problem if you don't mix them too carelessly. Ciao, Martin
Beitrag #5565357 wurde von einem Moderator gelöscht.
Beitrag #5565360 wurde von einem Moderator gelöscht.
Beitrag #5565362 wurde von einem Moderator gelöscht.
Beitrag #5565368 wurde von einem Moderator gelöscht.
Wobei a in f(int a[10]) zwar wie ein Array aussieht, aber in keiner Weise eines ist. Den Unterschied sieht man bei sizeof. Bei echten Arrays gibts die Grösse vom gesamten Array, bei einem Pointer die vom Pointer. Diese Form der Parameterdeklaration ist schlicht grober Unfug.
:
Bearbeitet durch User
Beitrag #5565412 wurde von einem Moderator gelöscht.
Beitrag #5565593 wurde von einem Moderator gelöscht.
Beitrag #5565669 wurde von einem Moderator gelöscht.
Bernd K. schrieb: > Aber ja, das ist nur wieder eine weitere häßliche Unvollkommenheit > dieser Sprache die sich einreiht in eine lange Liste weiterer > Unvollkommenheiten. Bernd, Du empfiehlst oben eine unkonventionelle Schreibweise, weil sie besser sei, und lässt dich dann darüber aus, wie schrecklich C ist, wenn man es denn so macht. Und dann empfiehlst Du auch noch Verschleierung mit typedef. Ja, kann man alles tun. Trägt hier allerdings nur zur Verwirrung bei. Es hat Gründe, warum Profis z.b. typedef nur für Basistypen und funktionsptr verwenden und nicht um Pointer nicht mehr von Strukturen unterscheiden zu können.
Achim S. schrieb: > Und dann empfiehlst Du auch noch Verschleierung mit typedef. Typedef soll auch bei mir nicht als Verschleierung dienen sondern das Verständnis erhöhen. Für sowas triviales wie oben hätt ich allerdings nicht extra noch ein typedef eingeführt, es sei denn ich brauche den selben Pointertypen noch an zig anderen Stellen und er zeigt auf etwas das besonders genug ist einen eigenen Namen zu verdienen oder ein treffenderer Name als "Zeiger auf irgendein int" würde die Verständlichkeit verbessern. > und nicht um Pointer nicht mehr von Strukturen unterscheiden zu können. Das wird nicht der Fall sein wenn man die Namen seiner Typen nicht gerade ziellos auswürfelt. Und Zeigertypen als solche - ganz egal auf was sie zeigen - sind absolut nichts ungewöhnliches. > Profis Und ich kenne übrigens "Profis" deren Code ist so unterirdisch daß es die Sau graust und ich kenne "Amateure" deren Code ist so vorbildlich daß er in Lehrbüchern abgedruckt werden könnte, das ist also schonmal überhaupt kein Kriterium, also bitte keine solchen leeren Worthülsen wenn es um die eigentliche Sache geht. Ich bin auch "Profi" (was auch immer das über die bloße Tatsache des Geldverdienens hinaus sonst noch zu bedeuten haben mag) und ich versuche meinen Code in erster Linie auf Lesbarkeit, Verständlichkeit und Wartbarkeit zu optimieren. Ich habe einen strengen Satz an äußerst strikt einzuhaltenden Konventionen an die ich mich genauso halte wie ich es von allen erwarte die diesen Code ebenfalls anfassen, in sehr weiten Teilen (99%) decken die sich mit den gängigen Styleguides in einigen wenigen Punkten erlaube ich mir gezielte Abweichungen von der "Norm" die ich alle einzeln genauestens begründen kann. Das mit der Pointerschreibweise ist so eine.
Bernd K. schrieb: > in einigen > wenigen Punkten erlaube ich mir gezielte Abweichungen von der "Norm" die > ich alle einzeln genauestens begründen kann. Das mit der > Pointerschreibweise ist so eine. Da empfinde ich deine Sichtweise als etwas absurd. int a; int *b; int a,*b; Die Bezeichner a und b beziehen sich beide auf den Datentype int. Das Value, welches verarbeitet werden will ist ein int. Die Verwendung des Bezeichners a ermöglicht den direkten Zugriff auf das int Value. Der Bezeichner b trägt das zusätzliche Attribut "Zeiger". Für den Zugriff, auf das int Value, ist eine Dereferenzierung nötig. --- Für mich gehört der Stern ganz eindeutig zum Bezeichner, nicht zum Datentype. Der Datentype des Value, mit dem ich dealen möchte, ist in beiden Fällen ein int. ---------------- Klugscheisservernichter schrieb im Beitrag #5565669: > Aber auch für dich: Das Speicherabbild von pointer und array > ist nicht gleich. Hat das irgendwer behauptet? Wenn dir nicht klar ist, welchen Fußabdruck ein Zeiger, oder ein Array, im Speicher hat, solltest du dir mal ein gutes C/C++ Buch anschaffen. Da sollte das erwähnt werden. (etwas Nachdenken könnte auch schon reichen) Tipp: Das Wort "Äquivalenz" wird dir u.A. bei Wikipedia erklärt.
Arduino Fanboy D. schrieb: > Für mich gehört der Stern ganz eindeutig zum Bezeichner, nicht zum > Datentype. So ist C ja auch definiert. Ob man das für klug hält oder nicht. Andere Sprachen, andere Deklarationstechnik. In C++ gibts allerdings eine Tradition, sowas als int& b; zu schreiben, obwohl das "&" genau wie "*" syntaktisch stärker an "b" als an "int" bindet.
:
Bearbeitet durch User
A. K. schrieb: > int& b; Du meinst Referenzen? In meiner kleinen Arduino C++ Welt kann ich weitestgehend auf Zeiger verzichten und stattdessen die Vorteile von Referenzen genießen. Profanes/Naives Beispiel:
1 | int array[] {4,5,9}; |
2 | |
3 | int &a = array[0]; |
4 | int &b = array[1]; |
5 | int &c = array[2]; |
Einmal anfassbar, als Array, oder einzeln als benannte Variable. Die Verwendung in C würde Zeiger, und damit Speicher, erfordern. Oder eben #define, was es aber auch nicht schöner/flexibler macht. In C++ gibts das kostenlos
Es ist eben C und nicht was anderes. Die übliche, pragmatische Konvention ist die Deklarationszeile
1 | Objekt_type Objekt, *Objekt_pointer; |
Beitrag #5566400 wurde von einem Moderator gelöscht.
Beitrag #5566414 wurde von einem Moderator gelöscht.
Bei mir angekommen ist, dass Funktionen nur Abbilder der Parameter erzeugen und ich deshalb mit Pointern arbeiten muss um etwas an meinen Variablen/Pointern zu ändern, welche ich als Parameter übergeben habe, wenn diese Veränderungen nach Funktionsabruf bestehen bleiben sollen. Ich habe allerdings noch Verständnisprobleme mit der Speicherverwaltung mittels malloc(). Ich habe eine Funktion geschrieben die mir ein array aus Sinuswerten erzeugt und einen pointer auf das erste Element zurückgibt: double *sinarrayGenerieren(){ double array[11]; for(int i = 0; i<11;i++){ array[i] = sin(2.0*PI/11*(i+1)); } return array; } Da erhielt ich aber die Warnung, dass ich eine lokale Variable zurückgebe. Die Warnung macht Sinn, weil meine Variable nach Aufruf der Funktion gelöscht wird und ich so auf einen Speicherort zeige, der jederzeit umgeschrieben werden kann weil nicht mehr zugewiesen, zumindest nach meinem jetzigen Verständnis. Daher habe ich es umgeschrieben und pointer verwendet: double *sinarrayGenerieren(){ double *array = malloc(sizeof(double)*11); for(int i = 0; i<11;i++){ array[i] = sin(2.0*PI/11*(i+1)); } return array; } Weiter unten im main Programm habe ich den speicher wieder freigegeben, aber erst als ich die Funktion aufgerufen hatte: double *sinustabelle = sinarrayGenerieren(); . . . free(sinustabelle); Ich habe nun zwei Fragen: 1. Zu der ersten Variante: Ich übergebe bei Funktionsaufruf ja einen pointer, in dem Fall *sinustabelle. Der pointer *array existiert ja nach Funktionsaufruf nicht mehr, der wurde doch aber durch return an *sinustabelle weitergegeben oder nicht ? 2. Zu der zweiten Variante: Erzeuge ich bei meiner Variante Memory Leaks ? ich reserviere in der Funktion speicher für einen pointer. Dann übergebe ich den pointer mit return und gebe den speicher für sinustabelle wieder frei. Aber der speicher für array wurde ja nicht freigegeben. Oder kann ich davon ausgehen, da sinustabelle auf den speicherort von array zeigt, dass dadurch der speicher von array freigegeben wird ? Ist ja die gleiche Adresse.
Marco D. schrieb: > double *sinarrayGenerieren() Eine leere Parameterliste ist nicht das, wonach es aussieht, sondern steht für eine unbekannte Parameterliste. Alternative ohne dynamische Speicherverwaltung:
1 | void sinarrayGenerieren(double *array){ |
2 | for(int i = 0; i<11;i++){ |
3 | array[i] = sin(2.0*PI/11*(i+1)); |
4 | }
|
5 | }
|
und dann
1 | double array[11]; |
2 | sinarrayGenerieren(array); |
1. ja 2. nein ABER: Schlechter Stil: Besser erst Speicher reservieren, dann Funktion aufrufen und Pointer übergeben:
1 | double *p = malloc(...); |
2 | sinarrayGenerieren(p); |
3 | ...
|
4 | free(p); |
merciless
Marco D. schrieb: > Da erhielt ich aber die Warnung, dass ich eine lokale Variable > zurückgebe. Mit Fug und Recht. Marco D. schrieb: > 1. Zu der ersten Variante: die ist kaputt. Das sagt ja auch schon die Warnung. Marco D. schrieb: > Oder kann ich davon ausgehen, da sinustabelle auf den > speicherort von array zeigt, dass dadurch der speicher von array > freigegeben wird ? Ist ja die gleiche Adresse. Solange du den Zeiger nicht manipulierst, ist alles gut.
Marco D. schrieb: > 1. Zu der ersten Variante: Ich übergebe bei Funktionsaufruf ja einen > pointer, in dem Fall *sinustabelle. Der pointer *array existiert ja nach > Funktionsaufruf nicht mehr, der wurde doch aber durch return an > *sinustabelle weitergegeben oder nicht ? Korrekt. Um genau zu sein übergibst du bei Funktionsaufruf gar nichts aber die Funktion übergibt einen Pointer wenn sie zurückkehrt. Es ist übrigens nie verkehrt zu checken ob malloc auch eine Speicheradresse zurückgegeben hat oder eben nicht, weil z.B. keiner mehr verfügbar war. Marco D. schrieb: > 2. Zu der zweiten Variante: Erzeuge ich bei meiner Variante Memory Leaks > ? ich reserviere in der Funktion speicher für einen pointer. Dann > übergebe ich den pointer mit return und gebe den speicher für > sinustabelle wieder frei. Aber der speicher für array wurde ja nicht > freigegeben. Oder kann ich davon ausgehen, da sinustabelle auf den > speicherort von array zeigt, dass dadurch der speicher von array > freigegeben wird ? Ist ja die gleiche Adresse. Ja, du kannst davon ausgehen, dass der Speicher freigegeben wird. Für free() zählt nur die Adresse auf die der Pointer zeigt, nicht der Pointer selber.
Dirk K. schrieb: > ABER: Schlechter Stil: Besser erst > Speicher reservieren, dann Funktion > aufrufen und Pointer übergeben: Inwiefern ist das besser, wenn in der Dokumentation der Funktion der dynamische Charakter des Rückgabewerts deutlich aufgeführt wird? Schlechter Stil ist allerdings die Verwendung von malloc ohne Kontrolle auf NULL. ;-)
Nochwas: Marco D. schrieb: > 1. Zu der ersten Variante: Ich übergebe bei Funktionsaufruf ja einen > pointer, in dem Fall *sinustabelle. Der pointer *array existiert ja nach > Funktionsaufruf nicht mehr, der wurde doch aber durch return an > *sinustabelle weitergegeben oder nicht ? Wenn du ein Array innerhalb einer Funktion deklarierst, so wird dieses Array auf dem Stack landen. Wenn die Funktion zurückkehrt, wird dieser Speicherbereich jedoch wieder freigegeben. Dein Pointer *array existiert zwar als *sinustabelle weiter aber der Inhalt des Arrays (auf dem Stack) kann beliebig überschrieben werden. Deshalb die Warnung und deshalb besser anders machen, z.B. wie A.K. vorgeschlagen hat.
Bernd K. schrieb: > Typedef soll auch bei mir nicht als Verschleierung dienen sondern das > Verständnis erhöhen. Für sowas triviales wie oben hätt ich allerdings > nicht extra noch ein typedef eingeführt, es sei denn ich brauche den > selben Pointertypen noch an zig anderen Stellen und er zeigt auf etwas > das besonders genug ist einen eigenen Namen zu verdienen oder ein > treffenderer Name als "Zeiger auf irgendein int" würde die > Verständlichkeit verbessern. Dann würde ich aber einen typedef für "irgendein int" machen und nicht für einen Zeiger darauf. Ich halte es nicht für sinnvoll, Zeiger hinter Typedefs zu verstecken, genauso wenig übrigens wie Arrays. A. K. schrieb: > In C++ gibts allerdings eine Tradition, sowas als > int& b; > zu schreiben, obwohl das "&" genau wie "*" syntaktisch stärker an "b" > als an "int" bindet. Für mich bindet zumindest das * ganz selbstverständlich an das "int". In C++ ist das übrigens auch üblich, das * an den Typ zu machen (und wird auch vom Erfinder der Sprache selbst so verwendet). Beim & kann ich die Argumentation eher nachvollziehen, da Referenzen in C++ im Gegensatz zu Zeigern keine eigenen Typen sind. Arduino Fanboy D. schrieb: > In meiner kleinen Arduino C++ Welt kann ich weitestgehend auf Zeiger > verzichten und stattdessen die Vorteile von Referenzen genießen. > Einmal anfassbar, als Array, oder einzeln als benannte Variable. > Die Verwendung in C würde Zeiger, und damit Speicher, erfordern. > Oder eben #define, was es aber auch nicht schöner/flexibler macht. > > In C++ gibts das kostenlos Das bekommst du aber (in C und C++) mit Zeigern genauso gut hin. Tatsächlich werden Referenzen in g++ hinter den Kulissen auf die gleiche Weise implementiert wie Zeiger - indem sie die Adresse des Zielobjekts enthalten. Die Optimierung schmeißt in deinem Programm dann sowohl für Zeiger als auch für Referenzen die zusätzliche Speicherung der Adresse raus. Arduino Fanboy D. schrieb: > Da empfinde ich deine Sichtweise als etwas absurd. Und ich deine - auch wenn es die gleiche sein mag, die K&R damals hatten, als sie das verbrochen haben. > int a; > int *b; > > int a,*b; > > Die Bezeichner a und b beziehen sich beide auf den Datentype int. Die Zeilen definieren jeweils eine Variable. Die eine definiert eine Variable vom Typ "int" mit Namen a, die andere eine vom Typ "Zeiger auf int" mit Name b. Meine Sichtweise sagt genau ds: int* : Zeiger auf int … b : … mit Namen "b" Deine klingt für mich so: int : Typ "int"… *b : … mit Namen "b", aber es soll dann doch kein int sein, sondern das was, wenn man es dereferenzieren würde, zu einem int führen würde. > Die Verwendung des Bezeichners a ermöglicht den direkten Zugriff auf das > int Value. > > Der Bezeichner b trägt das zusätzliche Attribut "Zeiger". Dieses "Attribut" ist aber ein elementarer Bestandteil des Typs von b. > Klugscheisservernichter schrieb: >> Aber auch für dich: Das Speicherabbild von pointer und array >> ist nicht gleich. > Hat das irgendwer behauptet? > > Wenn dir nicht klar ist, welchen Fußabdruck ein Zeiger, oder ein Array, > im Speicher hat, solltest du dir mal ein gutes C/C++ Buch anschaffen. Da > sollte das erwähnt werden. > (etwas Nachdenken könnte auch schon reichen) > > Tipp: > Das Wort "Äquivalenz" wird dir u.A. bei Wikipedia erklärt. Irgendwie diskutiert ihr aneinander vorbei. Einer sagt: "Ein Auto hat vier Räder" und der andere sagt: "Nein, es hat einen Motor". A. K. schrieb: > Dirk K. schrieb: > >> ABER: Schlechter Stil: Besser erst >> Speicher reservieren, dann Funktion >> aufrufen und Pointer übergeben: > > Inwiefern ist das besser, wenn in der Dokumentation der Funktion der > dynamische Charakter des Rückgabewerts deutlich aufgeführt wird? Wenn der Aufrufer den Speicher bereitstellt, hat das erstens den Vorteil, dass der sich selber raussuchen kann, wo er diesen Speicher hält, und zweitens ist er dann sowohl für die Allokation, als auch für die Freigabe verantwortlich. Da muss der Nutzer nicht mal die Doku lesen, um zu wissen, dass er den Speicher wieder freigeben muss, wenn er ihn selber allokiert. > Schlechter Stil ist allerdings die Verwendung von malloc ohne Kontrolle > auf NULL. ;-) Diese Kontrolle bringt auf dem PC in der Praxis nicht viel. Erstens versinkt der eh in der Auslagerungshölle, schon lange bevor malloc NULL zurückgibt, zweitens ist die Allokation auf Systemen mit Memory Overcommit sowieso auch dann erfolgreich, wenn eigentlich kein Speicher mehr verfügbar ist.
Rolf M. schrieb: > Für mich bindet zumindest das * ganz selbstverständlich an das "int". Für den Parser des Compilers bindet es stärker an den Namen als an den Grundtyp. Andernfalls wäre q in int*p,q auch ein Pointer. Sinngemäss (keine korrekte Syntax): int *p,q ist int (*p),q und nicht (int *) p,q > da Referenzen in C++ im Gegensatz zu Zeigern keine eigenen Typen sind. Was meinst du damit?
:
Bearbeitet durch User
Rolf M. schrieb: >> Schlechter Stil ist allerdings die Verwendung von malloc ohne Kontrolle >> auf NULL. ;-) > > Diese Kontrolle bringt auf dem PC in der Praxis nicht viel. Wir sind im Forenzweig für Mikrocontroller, nicht dem für PCs. ;-)
A. K. schrieb: > Rolf M. schrieb: >> Für mich bindet zumindest das * ganz selbstverständlich an das "int". > > Für den Parser des Compilers bindet es stärker an den Namen als an den > Grundtyp. Andernfalls wäre q in int*p,q auch ein Pointer. Ja, in der Hinsicht hast du natürlich recht. Ich hatte "binden" nicht im Sinne der Parsers verstanden, sondern in dem Sinne, zu was das * eher gehört. >> da Referenzen in C++ im Gegensatz zu Zeigern keine eigenen Typen sind. > > Was meinst du damit? War vielleicht etwas falsch ausgedrückt, bzw. ich hab etwas durcheinander gebracht. Natürlich gibt es Referenz-Typen, aber Referenzen sind selbst keine Objekte.
:
Bearbeitet durch User
Rolf M. schrieb: > Ja, in der Hinsicht hast du natürlich recht. Ich hatte "binden" nicht im > Sinne der Parsers verstanden, sondern in dem Sinne, zu was das * eher > gehört. Vielleicht hatte ich zu viel mit dem Inneren von Compilern zu tun, um noch derart menschlich denken zu können. ;-) > Natürlich gibt es Referenz-Typen, aber Referenzen sind selbst keine > Objekte. Ok. Platz brauchen diese Nicht-Objekte aber trotzdem.
:
Bearbeitet durch User
Beitrag #5566809 wurde von einem Moderator gelöscht.
A. K. schrieb: >> Natürlich gibt es Referenz-Typen, aber Referenzen sind selbst keine >> Objekte. > > Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. Sie haben keine Größe im Speicher (sizeof liefert die Größe des referenzierten Objekts, man kann sie nicht dynamisch allokieren, es gibt keine Arrays aus Referenzen), aber ihre Umsetzung kann natürlich Speicher benötigen. Das ist in der Hinsicht ähnlich wie bei Funktionen.
Beitrag #5566900 wurde von einem Moderator gelöscht.
A. K. schrieb: > Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. Das Objekt, ja. Aber nicht dessen Referenz. Im C gibt es übrigens einen workaround.... Ein [1] dahinter, dann ist das Objekt bei Funktionsübergabe nur sein Zeiger, ...
Rolf M. schrieb: >> Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. > > Sie haben keine Größe im Speicher Wenn etwas keine Grösse hat, aber Platz braucht, in welcher philosophischen Sphäre bewegen wir uns dann? Diese Struct ist jedenfalls gemäss sizeof 8 Bytes gross (x64):
1 | struct S { |
2 | int& y; |
3 | };
|
Zur Sprache: Welcher Begriff passt zum Member y selbst, dem Ding in der Sprache mit dem Namen y. Was ist das? Wird sonst schwierig mit der Diskussion. Denn y braucht Platz, wie immer es heisst. Für mich gibts jedenfalls einen Unterschied zwischen dem Member y und der Verwendung des Members in Ausdrücken.
:
Bearbeitet durch User
Achim S. schrieb: > A. K. schrieb: >> Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. > > Das Objekt, ja. Aber nicht dessen Referenz. Der c++17 Standard (N4687) sagt dazu:
1 | 4.4 The C ++ memory model [intro.memory] |
2 | ... |
3 | 3 A memory location is either an object of scalar type or a maximal sequence of adjacent bit-fields all having |
4 | nonzero width. [ Note: Various features of the language, such as references and virtual functions, might involve |
5 | additional memory locations that are not accessible to programs but are managed by the implementation. |
6 | — end note ] Two or more threads of execution (4.7) can access separate memory locations without interfering |
7 | with each other. |
8 | ... |
und:
1 | 11.3.2 References [dcl.ref] |
2 | ... |
3 | 4 It is unspecified whether or not a reference requires storage (6.7). |
4 | ... |
A. K. schrieb: > Rolf M. schrieb: >>> Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. >> >> Sie haben keine Größe im Speicher > > Diese Struct ist 8 Bytes gross (x64):struct S { > int& y; > }; > > Zur Sprache: Welcher Begriff passt zum Member y selbst, dem Ding in der > Sprache mit dem Namen y. Was ist das? Wird sonst schwierig mit der > Diskussion. Denn y braucht Platz, wie immer es heisst. y ist eine reference, bzw genauer eine lvalue reference.
mh schrieb: > 4 It is unspecified whether or not a reference requires storage (6.7). Der Satz gefällt mir wesentlich besser als obiges "... haben keine Grösse im Speicher".
Nachtrag zu deinem Edit A. K. schrieb: > Für mich gibts jedenfalls einen Unterschied zwischen dem Member y und > der Verwendung des Members in Ausdrücken. Ja, erst in einem Ausdruck steht die Referenz für das Objekt selbst.
1 | 8 Expressions [expr] |
2 | ... |
3 | 5 |
4 | If an expression initially has the type “reference to T” (11.3.2, 11.6.3), the type is adjusted to T prior to |
5 | any further analysis. The expression designates the object or function denoted by the reference, and the |
6 | expression is an lvalue or an xvalue, depending on the expression. [ Note: Before the lifetime of the reference |
7 | has started or after it has ended, the behavior is undefined (see 6.8). — end note ] |
8 | |
9 | ... |
A. K. schrieb: > mh schrieb: >> 4 It is unspecified whether or not a reference requires storage (6.7). > > Der Satz gefällt mir wesentlich besser als obiges "... haben keine > Grösse im Speicher". Vermutlich haben die Verantwortlichen auch etwas länger über die Formulierung nachgedacht ;-)
A. K. schrieb: > Rolf M. schrieb: >>> Ok. Platz brauchen diese Nicht-Objekte aber trotzdem. >> >> Sie haben keine Größe im Speicher > > Wenn etwas keine Grösse hat, aber Platz braucht, in welcher > philosophischen Sphäre bewegen wir uns dann? Man braucht dafür keine Philosophie, nur Abstraktion. > Diese Struct ist jedenfalls > gemäss sizeof 8 Bytes gross (x64): > struct S { > int& y; > }; Und wie groß ist die Referenz gemäß sizeof? Welches Alginment hat eine Referenz? Auf diese Fragen gibt es auf der abstrakten Ebene von C++ keine Antwort. Man kann das so sehen: Die Referenz ist kein Objekt und hat damit keine spezifische Größe im Speicher, aber um ihre Funktion umzusetzen, müssen der Struktur, die sie enthält, Informationen hinzugefügt werden. Etwa so wie bei virtuellen Memberfunktionen, wo das Objekt meist auch größer wird, wenn man der Klasse eine virtuelle Memberfunktion hinzufügt, da sie einen versteckten vtable-Zeiger enthält. Bei der Referenz enthält die Klasse eben einen versteckten Zeiger auf das zu referenzierende Objekt. > Zur Sprache: Welcher Begriff passt zum Member y selbst, dem Ding in der > Sprache mit dem Namen y. Was ist das? Eine Referenz. Formal auf C++-Ebene enthält eine Referenz keine Adresse, sondern ist lediglich ein anderer Name für ein bereits existierendes Objekt, so ähnlich wie ein typedef ein anderer Name für einen existierenden Typ ist. Wie das implementiert ist, bleibt dabei aber komplett dem Compiler überlassen. Die Speicherung der Adresse des zu referenzierenden Objekts ist dabei lediglich eine recht offensichtliche Möglichkeit, das zu tun, aber nirgends vorgeschrieben.
:
Bearbeitet durch User
A. K. schrieb: > Inwiefern ist das besser, wenn in der Dokumentation der Funktion der > dynamische Charakter des Rückgabewerts deutlich aufgeführt wird? > > Schlechter Stil ist allerdings die Verwendung von malloc ohne Kontrolle > auf NULL. ;-) Du gibst die Ownership für den Speicher weiter. Das kann leicht übersehen werden. Außerdem wird die Wiederverwendbarkeit der Funktion eingeschränkt. Schlechter Stil ist die Verwendung von malloc() überhaupt: Würde ich nur unter Androhung von Waffengewalt so implementieren, ich würde C++ mit Smart-Pointern verwenden oder das Array auf den Stack packen. Ich habe lediglich den Code des TO angepasst. merciless
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.