Was fällt den Leuten an C-Zeigern schwer? [geschlossen]

173

Aus der Anzahl der hier gestellten Fragen geht hervor, dass die Leute einige ziemlich grundlegende Probleme haben, wenn sie sich mit Zeigern und Zeigerarithmetik beschäftigen.

Ich bin gespannt warum. Sie haben mir nie wirklich große Probleme bereitet (obwohl ich sie bereits im Neolithikum kennengelernt habe). Um bessere Antworten auf diese Fragen zu schreiben, würde ich gerne wissen, was die Leute schwierig finden.

Also, wenn Sie mit Zeigern zu kämpfen haben oder Sie es kürzlich aber plötzlich "verstanden" haben, was waren die Aspekte von Zeigern, die Ihnen Probleme bereiteten?

Paul
quelle
54
Snarky, übertrieben, unnötig kontrovers, antwortet mit einem Körnchen Wahrheit: Sie wurden verkrüppelt - geistig verstümmelt, sage ich dir -, indem ich zuerst eine von ihnen "ausdrucksstarke" Hochsprachen warne. Sie sollten damit beginnen, auf Bare Metal zu programmieren, wie Gott es von Daniel Boone beabsichtigt hatte!
dmckee --- Ex-Moderator Kätzchen
3
... und Programmer wäre besser, weil es sich trotz aller Bemühungen zu einer Diskussion entwickeln wird .
dmckee --- Ex-Moderator Kätzchen
Dies ist argumentativ und subjektiv, es erzeugt N Ergebnisse, meine Schwierigkeit ist Ihre leichte. Es wird wahrscheinlich auf Programmierern funktionieren, aber wir migrieren diese Fragen dort noch nicht, da die Site nicht aus der Beta ist.
Sam Saffron
2
@ Sam Saffron: Obwohl ich im Allgemeinen der Meinung bin, dass dies eher eine Frage vom Typ Programmierer ist, wäre es ehrlich gesagt nicht schlecht, wenn die Leute bereit wären, "Ich denke, es ist einfach" und "Ich hasse es, Zeiger zu sehen" als Spam zu markieren Sie sind.
Jkerian
3
Jemand muss dies ansprechen: "Es ist wie ein Finger, der zum Mond zeigt. Konzentrieren Sie sich nicht auf den Finger, sonst werden Sie all diese himmlische Herrlichkeit verpassen." - Bruce Lee
mu ist

Antworten:

86

Ich vermute, die Leute gehen etwas zu tief in ihre Antworten. Ein Verständnis der Zeitplanung, des tatsächlichen CPU-Betriebs oder der Speicherverwaltung auf Baugruppenebene ist nicht unbedingt erforderlich.

Als ich unterrichtete, stellte ich fest, dass die folgenden Lücken im Verständnis der Schüler die häufigste Ursache für Probleme sind:

  1. Heap vs Stack-Speicher. Es ist einfach erstaunlich, wie viele Menschen dies selbst im allgemeinen Sinne nicht verstehen.
  2. Rahmen stapeln. Nur das allgemeine Konzept eines dedizierten Abschnitts des Stapels für lokale Variablen, zusammen mit dem Grund, warum es sich um einen 'Stapel' handelt ... Details wie das Verstecken des Rückgabestandorts, Details zum Ausnahmebehandlungsroutine und frühere Register können sicher belassen werden, bis jemand versucht, dies zu tun Erstellen Sie einen Compiler.
  3. "Speicher ist Speicher ist Speicher" Beim Casting wird nur geändert, welche Versionen von Operatoren oder wie viel Platz der Compiler für einen bestimmten Speicherblock bietet. Sie wissen, dass Sie mit diesem Problem zu tun haben, wenn Leute darüber sprechen, "was (primitive) Variable X wirklich ist".

Die meisten meiner Schüler waren in der Lage, eine vereinfachte Zeichnung eines Speicherblocks zu verstehen, im Allgemeinen den Abschnitt mit den lokalen Variablen des Stapels im aktuellen Bereich. Im Allgemeinen half es, den verschiedenen Orten explizite fiktive Adressen zu geben.

Zusammenfassend denke ich, wenn Sie Zeiger verstehen wollen, müssen Sie Variablen verstehen und wissen, was sie in modernen Architekturen tatsächlich sind.

jkerian
quelle
13
IMHO ist das Verständnis von Stack und Heap ebenso unnötig wie CPU-Details auf niedriger Ebene. Stapel und Heap sind Implementierungsdetails. In der ISO C-Spezifikation wird das Wort "Stapel" nicht einmal erwähnt, und K & R auch nicht.
Sigjuice
4
@sigjuice: Ihre Einwände verfehlen den Punkt sowohl der Frage als auch der Antwort. A) K & R C ist ein Anachronismus B) ISO C ist nicht die einzige Sprache mit Zeigern. Meine Punkte 1 und 2 wurden gegen eine nicht C-basierte Sprache entwickelt. C) 95% der Architekturen (keine Sprachen) verwenden den Heap / stack system ist es häufig genug, dass Ausnahmen relativ dazu erklärt werden. D) Der Punkt der Frage war "warum verstehen die Leute keine Zeiger", nicht "wie erkläre ich ISO C"
jkerian
9
@ John Marchetti: Umso mehr ... angesichts der Frage "Was ist das Grundproblem mit dem Problem der Leute mit Zeigern?" Glaube ich nicht, dass die Leute, die die zeigerbezogenen Fragen stellen, schrecklich beeindruckt wären von "You don '". Ich muss es wirklich wissen "als Antwort. Offensichtlich sind sie nicht einverstanden. :)
Jkerian
3
@jkerian Es mag veraltet sein, aber die drei oder vier Seiten von K & R, die Zeiger erklären, tun dies, ohne dass Implementierungsdetails erforderlich sind. Die Kenntnis der Implementierungsdetails ist aus verschiedenen Gründen nützlich, aber meiner Meinung nach sollte dies keine Voraussetzung für das Verständnis der Schlüsselkonstrukte einer Sprache sein.
Sigjuice
3
"Im Allgemeinen hat es geholfen, den verschiedenen Orten explizite fiktive Adressen zu geben." -> +1
Fredoverflow
146

Als ich anfing, mit ihnen zu arbeiten, war das größte Problem, das ich hatte, die Syntax.

int* ip;
int * ip;
int *ip;

sind alle gleich.

aber:

int* ip1, ip2;  //second one isn't a pointer!
int *ip1, *ip2;

Warum? weil der "Zeiger" -Teil der Deklaration zur Variablen und nicht zum Typ gehört.

Und dann die Dereferenzierung des Dings verwendet eine sehr ähnliche Notation:

*ip = 4;  //sets the value of the thing pointed to by ip to '4'
x = ip;   //hey, that's not '4'!
x = *ip;  //ahh... there's that '4'

Außer wenn Sie tatsächlich einen Zeiger benötigen ... dann verwenden Sie ein kaufmännisches Und!

int *ip = &x;

Hurra für die Konsistenz!

Dann, anscheinend nur, um Idioten zu sein und zu beweisen, wie klug sie sind, verwenden viele Bibliotheksentwickler Zeiger-zu-Zeiger-zu-Zeiger, und wenn sie eine Reihe dieser Dinge erwarten, warum nicht auch einfach einen Zeiger darauf übergeben? .

void foo(****ipppArr);

Um dies aufzurufen, benötige ich die Adresse des Array von Zeigern auf Zeiger auf Zeiger von Ints:

foo(&(***ipppArr));

In sechs Monaten, wenn ich diesen Code pflegen muss, werde ich mehr Zeit damit verbringen, herauszufinden, was dies alles bedeutet, als von Grund auf neu zu schreiben. (Ja, wahrscheinlich habe ich diese Syntax falsch verstanden - es ist eine Weile her, seit ich irgendetwas in C gemacht habe. Ich vermisse es irgendwie, aber dann bin ich ein bisschen ein Massochist)

Jeff Knecht
quelle
21
Ihr Kommentar zum ersten, >> * ip = 4; // setzt den Wert von ip auf '4' << ist falsch. Es sollte >> // sein, setzt den Wert der Sache, auf die ip zeigt, auf '4'
aaaa bbbb
8
Es ist in jeder Sprache eine schlechte Idee, zu viele Typen übereinander zu stapeln. Möglicherweise schreiben Sie "foo (& (*** ipppArr));" seltsam in C, aber etwas wie "std :: map <std :: pair <int, int>, std :: pair <std :: vector <int>, std :: tuple <int, double, std :: list" zu schreiben <int> >>> "in C ++ ist ebenfalls sehr komplex. Dies bedeutet nicht, dass Zeiger in C- oder STL-Containern in C ++ komplex sind. Es bedeutet nur, dass Sie bessere Typdefinitionen verwenden müssen, um es für den Leser Ihres Codes verständlich zu machen.
Patrick
20
Ich kann aufrichtig nicht glauben, dass ein Missverständnis der Syntax die am häufigsten gewählte Antwort ist. Das ist der einfachste Teil über Zeiger.
Jason
4
Selbst als ich diese Antwort las, war ich versucht, ein Blatt Papier zu holen und das Bild zu zeichnen. In C habe ich immer Bilder gezeichnet.
Michael Easter
19
@ Jason Welches objektive Maß für die Schwierigkeit gibt es außer dem, was die Mehrheit der Menschen schwierig findet?
Rupert Madden-Abbott
52

Das richtige Verständnis von Zeigern erfordert Kenntnisse über die Architektur der zugrunde liegenden Maschine.

Viele Programmierer wissen heute nicht, wie ihre Maschine funktioniert, genauso wie die meisten Leute, die wissen, wie man ein Auto fährt, nichts über den Motor wissen.

Robert Harvey
quelle
18
@dmckee: Nun, irre ich mich? Wie viele Java-Programmierer könnten mit einem Segfault umgehen?
Robert Harvey
5
Haben Segfaults etwas mit Schalthebel zu tun? - Ein Java-Programmierer
Tom Anderson
6
@ Robert: Es war als echte Ergänzung gedacht. Dies ist ein schwieriges Thema, ohne die Gefühle der Menschen zu verletzen. Und ich fürchte, mein Kommentar hat genau den Konflikt ausgelöst, den Sie zu vermeiden glaubten. Mea cupla.
dmckee --- Ex-Moderator Kätzchen
30
Ich bin nicht einverstanden; Sie müssen die zugrunde liegende Architektur nicht verstehen, um Zeiger zu erhalten (sie sind sowieso eine Abstraktion).
Jason
11
@ Jason: In C ist ein Zeiger im Wesentlichen eine Speicheradresse. Ohne die Maschinenarchitektur zu verstehen, ist es unmöglich, sicher mit ihnen zu arbeiten. Siehe en.wikipedia.org/wiki/Pointer_(computing) und gelangweilt.org/pointers/#definition
Robert Harvey
42

Wenn es um Hinweise geht, befinden sich verwirrte Personen in einem von zwei Lagern. Ich war (bin?) In beiden.

Die array[]Menge

Dies ist die Menge, die direkt nicht weiß, wie sie von der Zeigernotation in die Arraynotation übersetzt werden soll (oder nicht einmal weiß, dass sie überhaupt verwandt sind). Es gibt vier Möglichkeiten, auf Elemente eines Arrays zuzugreifen:

  1. Array-Notation (Indizierung) mit dem Array-Namen
  2. Array-Notation (Indizierung) mit dem Zeigernamen
  3. Zeigernotation (das *) mit dem Zeigernamen
  4. Zeigernotation (das *) mit dem Arraynamen

 

int vals[5] = {10, 20, 30, 40, 50};
int *ptr;
ptr = vals;

array       element            pointer
notation    number     vals    notation

vals[0]     0          10      *(ptr + 0)
ptr[0]                         *(vals + 0)

vals[1]     1          20      *(ptr + 1)
ptr[1]                         *(vals + 1)

vals[2]     2          30      *(ptr + 2)
ptr[2]                         *(vals + 2)

vals[3]     3          40      *(ptr + 3)
ptr[3]                         *(vals + 3)

vals[4]     4          50      *(ptr + 4)
ptr[4]                         *(vals + 4)

Die Idee dabei ist , dass Arrays über Zeiger zugreifen scheint ziemlich einfach und unkompliziert, aber eine Tonne sehr kompliziert und kluge Dinge kann auf diese Weise durchgeführt werden. Einige davon können erfahrene C / C ++ - Programmierer verwirren, geschweige denn unerfahrene Neulinge.

Die reference to a pointerund pointer to a pointerMenge

Dies ist ein großartiger Artikel, der den Unterschied erklärt und dem ich Code zitieren und stehlen werde :)

Als kleines Beispiel kann es sehr schwierig sein, genau zu sehen, was der Autor tun wollte, wenn Sie auf so etwas stoßen:

//function prototype
void func(int*& rpInt); // I mean, seriously, int*& ??

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(pvar);
  ....
  return 0;
}

Oder in geringerem Maße so etwas:

//function prototype
void func(int** ppInt);

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(&pvar);
  ....
  return 0;
}

Was lösen wir am Ende des Tages wirklich mit all dem Kauderwelsch? Nichts.

Jetzt haben wir die Syntax von ptr-to-ptr und ref-to-ptr gesehen. Gibt es irgendwelche Vorteile gegenüber dem anderen? Ich fürchte, nein. Die Verwendung von einem von beiden ist für einige Programmierer nur eine persönliche Präferenz. Einige, die ref-to-ptr verwenden, sagen, die Syntax sei "sauberer", während andere, die ptr-to-ptr verwenden, sagen, dass die ptr-to-ptr-Syntax denjenigen, die lesen, was Sie tun, klarer macht.

Diese Komplexität und die scheinbare (kühn erscheinende) Austauschbarkeit mit Referenzen, die häufig eine weitere Einschränkung von Zeigern und ein Fehler von Neuankömmlingen darstellt, erschweren das Verständnis von Zeigern. Es ist auch wichtig zu verstehen, um ihrer selbst willen Abschluss, dass Hinweise auf Referenzen illegal sind in C und C ++ aus Gründen verwirrend , die Sie in nehmen lvalue- rvalueSemantik.

Wie eine frühere Antwort bemerkte, haben Sie oft nur diese Hotshot-Programmierer, die denken, dass sie klug sind, ******awesome_var->lol_im_so_clever()und die meisten von uns sind wahrscheinlich schuldig, manchmal solche Gräueltaten zu schreiben, aber es ist einfach kein guter Code, und es ist sicherlich nicht wartbar .

Nun, diese Antwort war länger als ich gehofft hatte ...

David Titarenco
quelle
5
Ich denke, Sie haben hier möglicherweise eine C ++ - Antwort auf eine C-Frage gegeben ... zumindest den zweiten Teil.
Detly
Zeiger auf Zeiger gelten auch für C: p
David Titarenco
1
Heh? Ich sehe nur Zeiger auf Zeiger, wenn ich Arrays herumgebe - Ihr zweites Beispiel ist nicht wirklich auf den anständigsten C-Code anwendbar. Außerdem ziehen Sie C in das Chaos von C ++ - Referenzen in C existieren nicht.
new123456
In vielen, vielen legitimen Fällen müssen Sie sich mit Zeigern auf Zeiger befassen. Zum Beispiel, wenn es sich um einen Funktionszeiger handelt, der auf eine Funktion zeigt, die einen Zeiger zurückgibt. Ein weiteres Beispiel: Eine Struktur, die eine variable Anzahl anderer Strukturen enthalten kann. Und viele mehr ...
David Titarenco
2
"Verweise auf Referenzen sind in C illegal" - eher wie "abwesend" :)
Kos
29

Ich beschuldige die Qualität der Referenzmaterialien und die Personen, die den Unterricht persönlich durchführen. Die meisten Konzepte in C (aber insbesondere Zeiger) werden einfach schlecht gelehrt . Ich drohe immer wieder, mein eigenes C-Buch zu schreiben (mit dem Titel Das Letzte, was die Welt braucht, ist ein weiteres Buch über die C-Programmiersprache ), aber ich habe weder die Zeit noch die Geduld, dies zu tun. Also hänge ich hier ab und werfe zufällige Zitate aus dem Standard auf Leute.

Es gibt auch die Tatsache, dass bei der ersten Entwicklung von C davon ausgegangen wurde, dass Sie die Maschinenarchitektur ziemlich detailliert verstanden haben, nur weil es bei Ihrer täglichen Arbeit keine Möglichkeit gab, dies zu vermeiden (der Speicher war so knapp und die Prozessoren so langsam Sie mussten verstehen, wie sich das, was Sie geschrieben haben, auf die Leistung auswirkte.

John Bode
quelle
3
Ja. 'int foo = 5; int * pfoo = & foo; Sehen Sie, wie nützlich das ist? OK, mach weiter ... 'Ich habe keine Zeiger verwendet, bis ich meine eigene doppelt verknüpfte Listenbibliothek geschrieben habe.
John Lopez
2
+1. Ich unterrichte CS100-Schüler und so viele ihrer Probleme wurden gelöst, indem ich die Zeiger auf verständliche Weise durchging.
Benzado
1
+1 für den historischen Kontext. Nachdem ich lange nach dieser Zeit angefangen habe, ist mir das nie in den Sinn gekommen.
Lumi
26

Es gibt einen großartigen Artikel, der die Vorstellung unterstützt, dass Zeiger auf Joel Spolskys Website - The Perils of JavaSchools - schwer zu finden sind .

[Haftungsausschluss - Ich bin per se kein Java-Hasser .]

Steve Townsend
quelle
2
@ Jason - das ist wahr, negiert aber nicht das Argument.
Steve Townsend
4
Spolsky sagt nicht, dass JavaSchools der Grund sind, warum Menschen Zeiger schwierig finden. Er sagt, dass sie zu Analphabeten mit Informatik-Abschluss führen.
Benzado
1
@benzado - fair point - mein kurzer Beitrag würde verbessert, wenn er "einen großartigen Artikel lesen würde, der die Vorstellung unterstützt, dass Zeiger schwer sind". Was der Artikel impliziert, ist, dass "ein CS-Abschluss von einer 'guten Schule'" kein so guter Prädiktor für den Erfolg eines Entwicklers ist wie früher, während "Zeiger verstehen" (und Rekursion) immer noch ist.
Steve Townsend
1
@ Steve Townsend: Ich denke, Sie verpassen den Punkt von Mr. Spolskys Argumentation.
Jason
2
@Steve Townsend: Herr Spolsky argumentiert, dass Java-Schulen eine Generation von Programmierern aufziehen, die keine Zeiger und Rekursionen kennen, und nicht, dass Zeiger aufgrund der Verbreitung von Java-Schulen schwierig sind. Wie Sie sagten "es gibt einen großartigen Artikel darüber, warum dies schwierig ist" und mit diesem Artikel verknüpft sind, scheinen Sie die letztere Interpretation zu haben. Vergib mir, wenn ich falsch liege.
Jason
24

Die meisten Dinge sind schwerer zu verstehen, wenn Sie nicht auf dem Wissen basieren, das "darunter" liegt. Als ich CS unterrichtete, wurde es viel einfacher, als ich meine Schüler mit dem Programmieren einer sehr einfachen "Maschine" begann, eines simulierten Dezimalcomputers mit Dezimal-Opcodes, dessen Speicher aus Dezimalregistern und Dezimaladressen bestand. Sie würden sehr kurze Programme einfügen, um beispielsweise eine Reihe von Zahlen hinzuzufügen, um eine Summe zu erhalten. Dann machten sie einen Schritt, um zu sehen, was geschah. Sie könnten die Eingabetaste gedrückt halten und zusehen, wie sie "schnell" läuft.

Ich bin sicher, fast jeder auf SO wundert sich, warum es nützlich ist, so einfach zu werden. Wir vergessen, wie es war, nicht zu programmieren. Wenn Sie mit einem solchen Spielzeugcomputer spielen, werden Konzepte eingeführt, ohne die Sie nicht programmieren können, z. B. die Idee, dass die Berechnung ein schrittweiser Prozess ist, bei dem eine kleine Anzahl grundlegender Grundelemente zum Erstellen von Programmen verwendet wird, und das Konzept des Speichers Variablen als Orte, an denen Zahlen gespeichert werden, an denen sich die Adresse oder der Name der Variablen von der darin enthaltenen Zahl unterscheidet. Es wird unterschieden zwischen dem Zeitpunkt, zu dem Sie das Programm aufrufen, und dem Zeitpunkt, zu dem es "ausgeführt" wird. Ich vergleiche das Programmieren mit dem Überqueren einer Reihe von "Geschwindigkeitsbegrenzungen", wie sehr einfachen Programmen, dann Schleifen und Unterprogrammen, dann Arrays, dann sequentiellen E / A, dann Zeigern und Datenstruktur.

Wenn Sie zu C kommen, sind die Zeiger verwirrend, obwohl K & R sie sehr gut erklärt hat. Ich habe sie in C gelernt, indem ich wusste, wie man sie liest - von rechts nach links. Wie wenn ich int *pin meinem Kopf sehe, sage ich " pzeigt auf ein int". C wurde als ein Schritt weiter als die Assemblersprache erfunden und das gefällt mir daran - es liegt nahe an diesem "Boden". Zeiger sind wie alles andere schwerer zu verstehen, wenn Sie diese Grundlage nicht haben.

Mike Dunlavey
quelle
1
Ein guter Weg, dies zu lernen, ist das Programmieren von 8-Bit-Mikrocontrollern. Sie sind leicht zu verstehen. Nehmen Sie die Atmel AVR-Controller. Sie werden sogar von gcc unterstützt.
Xenu
@ Xenu: Ich stimme zu. Für mich war es Intel 8008 und 8051 :-)
Mike Dunlavey
Für mich war es ein benutzerdefinierter 8-Bit-Computer (das "Vielleicht") am MIT im Nebel der Zeit.
QuantumMechanic
Mike - Sie sollten Ihren Schülern einen KARDIAK besorgen :)
QuantumMechanic
1
@ Quantum: CARDIAC - gut, hatte noch nichts davon gehört. Das "Vielleicht" - lassen Sie mich raten, war das, als Sussman (et al.) Leute das Mead-Conway-Buch lesen und ihre eigenen LSI-Chips ficken ließ? Das war kurz nach meiner Zeit dort.
Mike Dunlavey
17

Ich habe keine Hinweise bekommen, bis ich die Beschreibung in K & R gelesen habe. Bis zu diesem Zeitpunkt machten Zeiger keinen Sinn. Ich habe eine ganze Reihe von Dingen gelesen, in denen die Leute sagten: "Lerne keine Hinweise, sie sind verwirrend und werden deinen Kopf verletzen und dir Aneurysmen geben." Also scheute ich mich lange davor und schuf diese unnötige Atmosphäre eines schwierigen Konzepts .

Ansonsten dachte ich meistens, warum um alles in der Welt sollten Sie eine Variable wollen, die Sie durchlaufen müssen, um den Wert zu erhalten, und wenn Sie ihr etwas zuweisen wollten, mussten Sie seltsame Dinge tun, um Werte zum Laufen zu bringen in sie. Der springende Punkt einer Variablen ist etwas, um einen Wert zu speichern, dachte ich. Warum jemand es kompliziert machen wollte, war mir ein Rätsel. "Also muss man mit einem Zeiger den *Operator benutzen , um seinen Wert zu erreichen ??? Was für eine doofe Variable ist das?" , Ich dachte. Sinnlos, kein Wortspiel beabsichtigt.

Der Grund, warum es kompliziert war, war, dass ich nicht verstand, dass ein Zeiger eine Adresse war für etwas war. Wenn Sie erklären, dass es sich um eine Adresse handelt, dass es sich um eine Adresse handelt, die eine Adresse für etwas anderes enthält, und dass Sie diese Adresse manipulieren können, um nützliche Dinge zu tun, könnte dies die Verwirrung beseitigen.

Eine Klasse, die die Verwendung von Zeigern für den Zugriff auf / das Ändern von Ports auf einem PC, die Verwendung von Zeigerarithmetik zur Adressierung verschiedener Speicherorte und die Betrachtung eines komplizierteren C-Codes, der ihre Argumente modifizierte, erforderte, machte mich von der Idee ab, dass Zeiger sinnlos waren.

J. Polfer
quelle
4
Wenn Sie nur über begrenzte Ressourcen (RAM, ROM, CPU) verfügen, z. B. in eingebetteten Anwendungen, sind Zeiger schnell viel sinnvoller.
Nick T
+1 für Nicks Kommentar - besonders für das Übergeben von Strukturen.
new123456
12

Hier ist ein Zeiger- / Array-Beispiel, das mir eine Pause gab. Angenommen, Sie haben zwei Arrays:

uint8_t source[16] = { /* some initialization values here */ };
uint8_t destination[16];

Ihr Ziel ist es, den Inhalt von uint8_t mit memcpy () vom Quellziel zu kopieren. Ratet mal, welche der folgenden Ziele erreicht werden:

memcpy(destination, source, sizeof(source));
memcpy(&destination, source, sizeof(source));
memcpy(&destination[0], source, sizeof(source));
memcpy(destination, &source, sizeof(source));
memcpy(&destination, &source, sizeof(source));
memcpy(&destination[0], &source, sizeof(source));
memcpy(destination, &source[0], sizeof(source));
memcpy(&destination, &source[0], sizeof(source));
memcpy(&destination[0], &source[0], sizeof(source));

Die Antwort (Spoiler Alert!) Ist ALLE. "Ziel", "& Ziel" und "& Ziel [0]" haben alle den gleichen Wert. "& Ziel" ist ein anderer Typ als die beiden anderen, aber es ist immer noch der gleiche Wert. Gleiches gilt für die Permutationen von "Quelle".

Nebenbei bevorzuge ich persönlich die erste Version.

Andrew Cottrell
quelle
Ich bevorzuge auch die erste Version (weniger Interpunktion).
Sigjuice
++ Ich auch, aber Sie müssen wirklich vorsichtig sein sizeof(source), denn wenn sourcees sich um einen Zeiger handelt, sizeofwird er nicht das sein, was Sie wollen. Ich schreibe manchmal (nicht immer), sizeof(source[0]) * number_of_elements_of_sourcenur um mich von diesem Fehler fernzuhalten.
Mike Dunlavey
Ziel, & Ziel & Ziel [0] sind überhaupt nicht gleich - aber jedes wird durch einen anderen Mechanismus in dieselbe Leere * konvertiert, wenn es in memcpy verwendet wird. Wenn Sie jedoch als Argument für sizeof verwendet werden, erhalten Sie zwei unterschiedliche Ergebnisse, und drei unterschiedliche Ergebnisse sind möglich.
Gnasher729
Ich dachte, die Adresse des Betreibers wäre erforderlich?
MarcusJ
7

Zunächst sollte ich sagen, dass C und C ++ die ersten Programmiersprachen waren, die ich gelernt habe. Ich habe mit C angefangen, dann viel C ++ in der Schule gemacht und bin dann zurück zu C gegangen, um fließend zu sprechen.

Das erste, was mich beim Lernen von C über Zeiger verwirrte, war das Einfache:

char ch;
char str[100];
scanf("%c %s", &ch, str);

Diese Verwirrung beruhte hauptsächlich darauf, dass die Verwendung eines Verweises auf eine Variable für OUT-Argumente eingeführt wurde, bevor mir Zeiger ordnungsgemäß eingeführt wurden. Ich erinnere mich, dass ich das Schreiben der ersten Beispiele in C for Dummies übersprungen habe, weil sie zu einfach waren, um nie das erste Programm, das ich geschrieben habe, zum Laufen zu bringen (höchstwahrscheinlich aus diesem Grund).

Was daran verwirrend war, war, was &cheigentlich bedeutete und warum stres nicht gebraucht wurde.

Nachdem ich mich damit vertraut gemacht hatte, erinnere ich mich, dass ich über die dynamische Zuordnung verwirrt war. Irgendwann wurde mir klar, dass Zeiger auf Daten ohne dynamische Zuweisung eines Typs nicht besonders nützlich waren, also schrieb ich etwas wie:

char * x = NULL;
if (y) {
     char z[100];
     x = z;
}

um zu versuchen, dynamisch etwas Speicherplatz zuzuweisen. Es hat nicht funktioniert. Ich war mir nicht sicher, ob es funktionieren würde, aber ich wusste nicht, wie es sonst funktionieren könnte.

Ich habe später von mallocund erfahren new, aber sie schienen mir wirklich magische Gedächtnisgeneratoren zu sein. Ich wusste nichts darüber, wie sie funktionieren könnten.

Einige Zeit später wurde mir wieder Rekursion beigebracht (ich hatte es vorher selbst gelernt, war aber jetzt im Unterricht) und ich fragte, wie es unter der Haube funktioniert - wo wurden die einzelnen Variablen gespeichert? Mein Professor sagte "auf dem Stapel" und viele Dinge wurden mir klar. Ich hatte den Begriff schon einmal gehört und zuvor Software-Stacks implementiert. Ich hatte schon lange zuvor gehört, dass andere sich auf "den Stapel" bezogen, aber ich hatte es vergessen.

Um diese Zeit wurde mir auch klar, dass die Verwendung mehrdimensionaler Arrays in C sehr verwirrend sein kann. Ich wusste, wie sie funktionierten, aber sie waren so leicht zu verwickeln, dass ich mich entschied, sie zu umgehen, wann immer ich konnte. Ich denke, dass das Problem hier hauptsächlich syntaktisch war (insbesondere das Übergeben oder Zurückgeben von Funktionen).

Seit ich in den nächsten ein oder zwei Jahren C ++ für die Schule geschrieben habe, habe ich viel Erfahrung mit Zeigern für Datenstrukturen. Hier hatte ich eine Reihe neuer Probleme - das Verwechseln von Zeigern. Ich hätte mehrere Ebenen von Zeigern (Dinge wie node ***ptr;), die mich stolpern lassen. Ich würde einen Zeiger falsch oft dereferenzieren und schließlich herausfinden, wie viele* ich durch Ausprobieren brauchte.

Irgendwann habe ich gelernt, wie der Haufen eines Programms funktioniert (irgendwie, aber gut genug, dass es mich nachts nicht mehr wach hält). Ich erinnere mich, dass ich gelesen habe, wenn Sie ein paar Bytes vor dem Zeiger suchen, der mallocauf einem bestimmten System zurückgegeben wird, können Sie sehen, wie viele Daten tatsächlich zugewiesen wurden. Ich erkannte, dass der Code in mallocmehr Speicher vom Betriebssystem verlangen konnte und dieser Speicher nicht Teil meiner ausführbaren Dateien war. Eine anständige Vorstellung davon zu haben, wie es mallocfunktioniert, ist wirklich nützlich.

Bald danach nahm ich an einer Montageklasse teil, die mir nicht so viel über Zeiger beibrachte, wie die meisten Programmierer wahrscheinlich denken. Es brachte mich dazu, mehr darüber nachzudenken, in welche Assembly mein Code übersetzt werden könnte. Ich hatte immer versucht, effizienten Code zu schreiben, aber jetzt hatte ich eine bessere Idee, wie es geht.

Ich nahm auch an einigen Kursen teil, in denen ich etwas Lispeln schreiben musste . Beim Schreiben von lisp ging es mir nicht so sehr um Effizienz wie in C. Ich hatte sehr wenig Ahnung, in was dieser Code übersetzt werden könnte, wenn er kompiliert wird, aber ich wusste, dass es so aussah, als würde man viele lokal benannte Symbole (Variablen) verwenden Dinge viel einfacher. Irgendwann schrieb ich einen AVL-Baumrotationscode in ein wenig Lisp, was mir aufgrund von Zeigerproblemen sehr schwer fiel, in C ++ zu schreiben. Ich erkannte, dass meine Abneigung gegen das, was ich für überschüssige lokale Variablen hielt, meine Fähigkeit, dieses und mehrere andere Programme in C ++ zu schreiben, behindert hatte.

Ich habe auch einen Compiler-Kurs besucht. Während in dieser Klasse blätterte ich in das vorgeschobenen Material voraus und darüber gelernt , statische Einzelbelegung (SSA) und toten Variablen, die außer nicht so wichtig ist , dass es hat mich gelehrt , dass jeder anständiger Compiler einen anständigen Job für den Umgang mit Variablen tun , die sind nicht mehr verwendet. Ich wusste bereits, dass mehr Variablen (einschließlich Zeiger) mit korrekten Typen und guten Namen mir helfen würden, die Dinge im Kopf zu behalten, aber jetzt wusste ich auch, dass es noch dümmer war, sie aus Effizienzgründen zu vermeiden, als meine weniger auf Mikrooptimierung ausgerichteten Professoren sagten mich.

Für mich hat es sehr geholfen, ein gutes Stück über das Speicherlayout eines Programms zu wissen. Das Überlegen, was mein Code symbolisch und auf der Hardware bedeutet, hilft mir dabei. Die Verwendung lokaler Zeiger mit dem richtigen Typ hilft sehr. Ich schreibe oft Code, der aussieht wie:

int foo(struct frog * f, int x, int y) {
    struct leg * g = f->left_leg;
    struct toe * t = g->big_toe;
    process(t);

Wenn ich also einen Zeigertyp vermassle, wird durch den Compilerfehler sehr deutlich, wo das Problem liegt. Wenn ich es tat:

int foo(struct frog * f, int x, int y) {
    process(f->left_leg->big_toe);

Wenn ein Zeigertyp falsch ist, ist der Compilerfehler viel schwieriger herauszufinden. Ich wäre versucht, in meiner Frustration auf Versuch und Irrtum-Änderungen zurückzugreifen und die Dinge wahrscheinlich noch schlimmer zu machen.

nategoose
quelle
1
+1. Gründlich und aufschlussreich. Ich hatte scanf vergessen, aber jetzt, wo Sie es ansprechen, erinnere ich mich an die gleiche Verwirrung.
Joe White
6

Rückblickend gab es vier Dinge, die mir wirklich geholfen haben, endlich Hinweise zu verstehen. Vorher konnte ich sie verwenden, aber ich habe sie nicht vollständig verstanden. Das heißt, ich wusste, wenn ich den Formularen folgen würde, würde ich die gewünschten Ergebnisse erzielen, aber ich verstand das „Warum“ der Formulare nicht vollständig. Mir ist klar, dass dies nicht genau das ist, was Sie gefragt haben, aber ich denke, es ist eine nützliche Folge.

  1. Schreiben einer Routine, die einen Zeiger auf eine Ganzzahl nahm und die Ganzzahl änderte. Dies gab mir die notwendigen Formen, auf denen ich mentale Modelle der Funktionsweise von Zeigern aufbauen konnte.

  2. Eindimensionale dynamische Speicherzuordnung. Durch das Herausfinden der 1-D-Speicherzuordnung habe ich das Konzept des Zeigers verstanden.

  3. Zweidimensionale dynamische Speicherzuordnung. Das Herausfinden der 2-D-Speicherzuordnung verstärkte dieses Konzept, lehrte mich aber auch, dass der Zeiger selbst Speicher benötigt und berücksichtigt werden muss.

  4. Unterschiede zwischen Stapelvariablen, globalen Variablen und Heapspeicher. Durch das Herausfinden dieser Unterschiede lernte ich die Arten von Speicher, auf die die Zeiger verweisen.

Für jeden dieser Punkte musste man sich vorstellen, was auf einer niedrigeren Ebene vor sich ging - ein mentales Modell aufbauen, das jeden Fall befriedigte, den ich mir vorstellen konnte, darauf zu werfen. Es hat Zeit und Mühe gekostet, aber es hat sich gelohnt. Ich bin überzeugt, dass man, um Zeiger zu verstehen, dieses mentale Modell darauf aufbauen muss, wie sie funktionieren und wie sie implementiert werden.

Nun zurück zu Ihrer ursprünglichen Frage. Basierend auf der vorherigen Liste gab es einige Punkte, die ich ursprünglich nur schwer erfassen konnte.

  1. Wie und warum sollte man einen Zeiger verwenden.
  2. Wie unterscheiden sie sich und sind dennoch Arrays ähnlich?
  3. Verstehen, wo die Zeigerinformationen gespeichert sind.
  4. Verstehen, worauf und wo der Zeiger zeigt.
Sparky
quelle
Hey, könntest du mich auf einen Artikel / ein Buch / deine Zeichnung / dein Gekritzel / irgendetwas verweisen, wo ich auf ähnliche Weise lernen könnte, wie du es in deiner Antwort beschrieben hast? Ich bin fest davon überzeugt, dass dies der richtige Weg ist, wenn man gut lernt, im Grunde alles. Tiefes Verständnis und gute mentale Modelle
Alexander Starbuck
1
@AlexStarbuck - Ich meine nicht, dass dies flippig klingt, aber die wissenschaftliche Methode ist ein großartiges Werkzeug. Zeichnen Sie sich ein Bild darüber, was Ihrer Meinung nach für ein bestimmtes Szenario passieren könnte. Programmieren Sie etwas, um es zu testen, und analysieren Sie, was Sie haben. Entsprach es Ihren Erwartungen? Wenn nicht, wo ist der Unterschied? Wiederholen Sie diesen Vorgang nach Bedarf und erhöhen Sie schrittweise die Komplexität, um sowohl Ihr Verständnis als auch Ihre mentalen Modelle zu testen.
Sparky
6

Ich hatte meinen "Zeiger-Moment" bei einigen Telefonieprogrammen in C. Ich musste einen AXE10-Austauschemulator mit einem Protokollanalysator schreiben, der nur das klassische C verstand. Alles hing davon ab, Zeiger zu kennen. Ich habe versucht, meinen Code ohne sie zu schreiben (hey, ich war "Pre-Pointer", habe mich etwas entspannt) und bin völlig gescheitert.

Der Schlüssel zum Verständnis war für mich der Operator & (Adresse). Sobald ich verstanden hatte, dass &idies die "Adresse von i" bedeutete, dann verstand ich das*i dass "der Inhalt der Adresse, auf die i zeigte", etwas später. Wann immer ich meinen Code schrieb oder las, wiederholte ich immer, was "&" bedeutete und was "*" bedeutete, und schließlich kam ich dazu, sie intuitiv zu verwenden.

Zu meiner Schande wurde ich gezwungen, VB und dann Java zu verwenden, damit mein Zeigerwissen nicht mehr so ​​scharf ist wie früher, aber ich bin froh, dass ich "Post-Pointer" bin. Bitten Sie mich jedoch nicht, eine Bibliothek zu verwenden, in der ich * * p verstehen muss.

Gary Rowe
quelle
Wenn &iist die Adresse und *ider Inhalt, was ist i?
Thomas Ahle
2
Ich überlade die Verwendung von i. Für eine beliebige Variable i bedeutet & i "die Adresse von" i, i allein bedeutet "den Inhalt von & i", und * i bedeutet "den Inhalt von & i als Adresse behandeln, zu dieser Adresse gehen und die zurückgeben Inhalt".
Gary Rowe
5

Die Hauptschwierigkeit bei Zeigern ist, zumindest für mich, dass ich nicht mit C angefangen habe. Ich habe mit Java angefangen. Die ganze Vorstellung von Zeigern war bis zu ein paar Klassen am College, in denen ich C kennen sollte, wirklich fremd. Dann brachte ich mir selbst die Grundlagen von C bei und wie man Zeiger in ihrem grundlegenden Sinne verwendet. Selbst dann muss ich jedes Mal, wenn ich C-Code lese, die Zeigersyntax nachschlagen.

In meiner sehr begrenzten Erfahrung (1 Jahr reale Welt + 4 im College) verwirren mich Zeiger, weil ich sie nie wirklich in etwas anderem als einem Klassenzimmer verwenden musste. Und ich kann mit den Schülern sympathisieren, die jetzt CS mit JAVA anstelle von C oder C ++ beginnen. Wie Sie sagten, haben Sie im neolithischen Zeitalter Hinweise gelernt und verwenden sie wahrscheinlich seitdem. Für uns neuere Menschen ist der Gedanke, Speicher zuzuweisen und Zeigerarithmetik zu betreiben, wirklich fremd, weil all diese Sprachen dies weg abstrahiert haben.

PS Nachdem er den Spolsky-Aufsatz gelesen hatte, war seine Beschreibung von 'JavaSchools' nichts anderes als das, was ich am College in Cornell ('05 -'09) durchgemacht habe. Ich nahm die Strukturen und funktionale Programmierung (sml), Betriebssysteme (C), Algorithmen (Stift und Papier) und eine ganze Reihe anderer Klassen, die nicht in Java unterrichtet wurden. Alle Intro-Klassen und Wahlfächer wurden jedoch alle in Java durchgeführt, da es sinnvoll ist, das Rad nicht neu zu erfinden, wenn Sie versuchen, etwas Höheres zu tun, als eine Hashtabelle mit Zeigern zu implementieren.

Schuhkarton639
quelle
4
Ehrlich gesagt, da Sie immer noch Schwierigkeiten mit Zeigern haben, bin ich mir nicht sicher, ob Ihre Erfahrung bei Cornell Joels Artikel im Wesentlichen widerspricht. Offensichtlich ist genug von Ihrem Gehirn in eine Java-Denkweise eingebunden, um seinen Standpunkt zu verdeutlichen.
Jkerian
5
Wat? Referenzen in Java (oder C # oder Python oder wahrscheinlich Dutzenden anderer Sprachen) sind nur Zeiger ohne Arithmetik. Zeiger zu verstehen bedeutet zu verstehen, warum void foo(Clazz obj) { obj = new Clazz(); }ein No-Op ist, während void bar(Clazz obj) { obj.quux = new Quux(); }das Argument mutiert wird ...
1
Ich weiß, was Referenzen in Java sind, aber ich sage nur, wenn Sie mich bitten, in Java zu reflektieren oder etwas Sinnvolles in CI zu schreiben, können Sie es nicht einfach herausholen. Es erfordert viel Forschung, wie es jedes Mal zum ersten Mal zu lernen.
Schuhkarton639
1
Wie kommt es, dass Sie eine Betriebssystemklasse in C durchlaufen haben, ohne C fließend zu sprechen? Nichts für ungut, ich erinnere mich nur daran, dass ich ein einfaches Betriebssystem von Grund auf neu entwickeln musste. Ich muss tausendmal Zeiger benutzt haben ...
Schwerkraft
5

Hier ist eine Nichtantwort: Verwenden Sie cdecl (oder c ++ decl), um es herauszufinden:

eisbaw@leno:~$ cdecl explain 'int (*(*foo)(const void *))[3]'
declare foo as pointer to function (pointer to const void) returning pointer to array 3 of int
Eisbaw
quelle
4

Sie fügen dem Code eine zusätzliche Dimension hinzu, ohne die Syntax wesentlich zu ändern. Denk darüber nach:

int a;
a = 5

Es gibt nur eine Sache zu ändern : a. Sie können schreiben a = 6und die Ergebnisse sind für die meisten Menschen offensichtlich. Aber jetzt überlegen Sie:

int *a;
a = &some_int;

Es gibt zwei Dinge a, die zu unterschiedlichen Zeiten relevant sind: den tatsächlichen Wert von a, den Zeiger und den Wert "hinter" dem Zeiger. Sie können ändern a:

a = &some_other_int;

... und some_intist immer noch irgendwo mit dem gleichen Wert. Sie können aber auch Folgendes ändern:

*a = 6;

Es gibt eine konzeptionelle Lücke zwischen a = 6, die nur lokale Nebenwirkungen hat und *a = 6die eine Reihe anderer Dinge an anderen Orten beeinflussen könnte. Mein Punkt hier ist nicht, dass das Konzept der Indirektion von Natur aus schwierig ist, sondern dass, weil Sie sowohl die unmittelbare, lokale als auch adie indirekte Sache mit *a... tun können, dies die Menschen verwirren könnte.

detly
quelle
4

Ich hatte ungefähr 2 Jahre in C ++ programmiert und dann auf Java konvertiert (5 Jahre) und habe nie zurückgeschaut. Als ich jedoch kürzlich einige native Dinge verwenden musste, stellte ich (mit Erstaunen) fest, dass ich nichts über Zeiger vergessen hatte und finde sie sogar einfach zu verwenden. Dies ist ein scharfer Kontrast zu dem, was ich vor 7 Jahren erlebt habe, als ich zum ersten Mal versuchte, das Konzept zu verstehen. Ich denke also, Verständnis und Sympathie sind eine Frage der Programmierreife? :) :)

ODER

Zeiger sind wie Fahrradfahren. Wenn Sie erst einmal herausgefunden haben, wie Sie mit ihnen arbeiten sollen, können Sie es nicht vergessen.

Alles in allem ist die gesamte Zeigeridee SEHR lehrreich und ich glaube, sie sollte von jedem Programmierer verstanden werden, unabhängig davon, ob er in einer Sprache mit Zeigern programmiert oder nicht.

Nikola Yovchev
quelle
3

Zeiger sind aufgrund der Indirektion schwierig.

Jason
quelle
"Es wird gesagt, dass es in der Informatik kein Problem gibt, das nicht durch eine weitere Indirektionsebene gelöst werden kann" (keine Ahnung, wer es zuerst gesagt hat)
The Archetypal Paul
Es ist wie Magie, wo die Fehlleitung die Leute verwirrt (aber total großartig ist)
Nick T
3

Zeiger (zusammen mit einigen anderen Aspekten der Arbeit auf niedriger Ebene) erfordern, dass der Benutzer die Magie wegnimmt.

Die meisten hochrangigen Programmierer mögen die Magie.

Paul Nathan
quelle
3

Zeiger sind eine Möglichkeit, mit dem Unterschied zwischen einem Handle für ein Objekt und einem Objekt selbst umzugehen. (ok, nicht unbedingt Objekte, aber du weißt was ich meine und wo mein Verstand ist)

Irgendwann müssen Sie sich wahrscheinlich mit dem Unterschied zwischen den beiden auseinandersetzen. In der modernen Hochsprache wird dies zur Unterscheidung zwischen Kopie nach Wert und Kopie nach Referenz. In jedem Fall ist es ein Konzept, das für Programmierer oft schwer zu verstehen ist.

Wie bereits erwähnt, ist die Syntax zur Behandlung dieses Problems in C hässlich, inkonsistent und verwirrend. Wenn Sie wirklich versuchen, es zu verstehen, ist schließlich ein Zeiger sinnvoll. Aber wenn Sie anfangen, sich mit Zeigern auf Zeiger usw. zu befassen, wird es sowohl für mich als auch für andere Menschen sehr verwirrend.

Ein weiterer wichtiger Punkt bei Zeigern ist, dass sie gefährlich sind. C ist die Sprache eines Master-Programmierers. Es setzt voraus, dass Sie wissen, was zum Teufel Sie tun, und gibt Ihnen dadurch die Macht, Dinge wirklich durcheinander zu bringen. Während einige Arten von Programmen noch in C geschrieben werden müssen, tun dies die meisten Programme nicht. Wenn Sie eine Sprache haben, die eine bessere Abstraktion für den Unterschied zwischen einem Objekt und seinem Handle bietet, empfehle ich Ihnen, sie zu verwenden.

In der Tat ist es in vielen modernen C ++ - Anwendungen häufig der Fall, dass jede erforderliche Zeigerarithmetik gekapselt und abstrahiert wird. Wir wollen nicht, dass Entwickler überall Zeigerarithmetik betreiben. Wir wollen eine zentralisierte, gut getestete API, die Zeigerarithmetik auf der untersten Ebene ausführt. Änderungen an diesem Code müssen mit großer Sorgfalt und umfangreichen Tests vorgenommen werden.

Samo
quelle
3

Ich denke, ein Grund, warum C-Zeiger schwierig sind, ist, dass sie mehrere Konzepte zusammenführen, die nicht wirklich gleichwertig sind. Da sie jedoch alle mithilfe von Zeigern implementiert werden, kann es für die Benutzer schwierig sein, die Konzepte zu entwirren.

In C werden Zeiger unter anderem verwendet:

  • Definieren Sie rekursive Datenstrukturen

In C definieren Sie eine verknüpfte Liste von Ganzzahlen wie folgt:

struct node {
  int value;
  struct node* next;
}

Der Zeiger ist nur da, weil dies die einzige Möglichkeit ist, eine rekursive Datenstruktur in C zu definieren, wenn das Konzept wirklich nichts mit einem so niedrigen Detail wie Speicheradressen zu tun hat. Betrachten Sie das folgende Äquivalent in Haskell, für das keine Zeiger erforderlich sind:

data List = List Int List | Null

Ziemlich einfach - eine Liste ist entweder leer oder besteht aus einem Wert und dem Rest der Liste.

  • Durch Strings und Arrays iterieren

So können Sie eine Funktion fooauf jedes Zeichen einer Zeichenfolge in C anwenden :

char *c;
for (c = "hello, world!"; *c != '\0'; c++) { foo(c); }

Obwohl auch ein Zeiger als Iterator verwendet wird, hat dieses Beispiel sehr wenig mit dem vorherigen gemeinsam. Das Erstellen eines Iterators, den Sie inkrementieren können, unterscheidet sich vom Definieren einer rekursiven Datenstruktur. Keines der beiden Konzepte ist besonders an die Idee einer Speicheradresse gebunden.

  • Polymorphismus erreichen

Hier ist eine aktuelle Funktionssignatur in glib :

typedef struct g_list GList;

void  g_list_foreach    (GList *list,
                 void (*func)(void *data, void *user_data),
                         void* user_data);

Whoa! Das ist ein ziemlicher Schluck void*. Und es geht nur darum, eine Funktion zu deklarieren, die über eine Liste iteriert, die alles Mögliche enthalten kann, und jedem Mitglied eine Funktion zuzuweisen. Vergleichen Sie es mit der mapDeklaration in Haskell:

map::(a->b)->[a]->[b]

Das ist viel einfacher: mapist eine Funktion, die eine Funktion verwendet, die ein ain ein konvertiert b, und sie auf eine Liste von a's anwendet, um eine Liste von ' s zu erhalten b. Genau wie in der C - Funktion g_list_foreach, mapbrauchte nichts in seiner eigenen Definition über die Arten kennen zu der sie angewandt werden.

Um zusammenzufassen:

Ich denke, C-Zeiger wären viel weniger verwirrend, wenn die Leute zuerst rekursive Datenstrukturen, Iteratoren, Polymorphismus usw. als separate Konzepte kennenlernen und dann lernen würden, wie Zeiger verwendet werden können, um diese Ideen in C zu implementieren , anstatt all diese zu mischen Konzepte zusammen zu einem einzigen Thema von "Zeigern".

gcbenison
quelle
Missbrauch c != NULLin Ihrem "Hallo Welt" Beispiel ... Sie meinen *c != '\0'.
Olaf Seibert
2

Ich denke, es erfordert eine solide Grundlage, wahrscheinlich auf Maschinenebene, mit einer Einführung in Maschinencode, Baugruppen und der Darstellung von Elementen und Datenstrukturen im RAM. Es dauert ein wenig Zeit, einige Hausaufgaben oder Problemlösungsübungen und einige Überlegungen.

Aber wenn eine Person zuerst Hochsprachen beherrscht (was nichts Falsches ist - ein Zimmermann benutzt eine Axt. Eine Person, die Atom spalten muss, benutzt etwas anderes. Wir brauchen Leute, die Tischler sind, und wir haben Leute, die Atome studieren) und Diese Person, die Hochsprache beherrscht, erhält eine 2-minütige Einführung in Zeiger, und dann ist es schwer zu erwarten, dass sie Zeigerarithmetik, Zeiger auf Zeiger, eine Reihe von Zeigern auf Zeichenfolgen variabler Größe und eine Reihe von Zeichen usw. versteht. Ein niedriges solides Fundament kann sehr hilfreich sein.

Unpolarität
quelle
2
Das Groken von Zeigern erfordert kein Verständnis des Maschinencodes oder der Baugruppe.
Jason
Benötigen, nein. Aber Menschen, die die Versammlung verstehen, würden wahrscheinlich viel, viel einfacher Hinweise finden, da sie bereits die meisten (wenn nicht alle) der notwendigen mentalen Verbindungen hergestellt haben.
CHao
2

Das Problem, das ich immer hatte (hauptsächlich Autodidakt), ist das "Wann", einen Zeiger zu verwenden. Ich kann mich mit der Syntax zum Erstellen eines Zeigers befassen, muss aber wissen, unter welchen Umständen ein Zeiger verwendet werden sollte.

Bin ich der einzige mit dieser Einstellung? ;-);

Larry
quelle
Ich verstehe das. Meine Antwort befasst sich irgendwie damit.
J. Polfer
2

Es war einmal ... Wir hatten 8-Bit-Mikroprozessoren und alle schrieben in Assembly. Die meisten Prozessoren enthielten eine Art indirekte Adressierung, die für Sprungtabellen und Kernel verwendet wurde. Als übergeordnete Sprachen hinzukamen, fügten wir eine dünne Abstraktionsebene hinzu und nannten sie Zeiger. Im Laufe der Jahre haben wir uns immer mehr von der Hardware entfernt. Das ist nicht unbedingt eine schlechte Sache. Sie werden aus einem bestimmten Grund als höhere Sprachen bezeichnet. Je mehr ich mich auf das konzentrieren kann, was ich tun möchte, anstatt auf die Details, wie es gemacht wird, desto besser.

Jim C.
quelle
2

Es scheint, dass viele Schüler ein Problem mit dem Konzept der Indirektion haben, insbesondere wenn sie das Konzept der Indirektion zum ersten Mal kennenlernen. Ich erinnere mich von damals, als ich Student war, dass von den +100 Studenten meines Kurses nur eine Handvoll Leute wirklich Hinweise verstanden haben.

Das Konzept der Indirektion wird im wirklichen Leben nicht oft verwendet, und daher ist es zunächst schwer zu verstehen.

Axilmar
quelle
2

Ich hatte vor kurzem gerade den Zeigerklick-Moment und war überrascht, dass ich ihn verwirrend fand. Es war mehr so, dass alle so viel darüber sprachen, dass ich davon ausging, dass dunkle Magie vor sich ging.

So wie ich es bekam, war es so. Stellen Sie sich vor, dass alle definierten Variablen zur Kompilierungszeit (auf dem Stapel) Speicherplatz erhalten. Wenn Sie ein Programm benötigen, das große Datendateien wie Audio oder Bilder verarbeiten kann, benötigen Sie keine feste Speichermenge für diese potenziellen Strukturen. Sie warten also bis zur Laufzeit, um dem Speichern dieser Daten (auf dem Heap) eine bestimmte Speichermenge zuzuweisen.

Sobald Sie Ihre Daten im Speicher haben, möchten Sie diese Daten nicht mehr jedes Mal um Ihren Speicherbus kopieren, wenn Sie eine Operation darauf ausführen möchten. Angenommen, Sie möchten einen Filter auf Ihre Bilddaten anwenden. Sie haben einen Zeiger, der an der Vorderseite der Daten beginnt, die Sie dem Bild zugewiesen haben, und eine Funktion läuft über diese Daten und ändert sie an Ort und Stelle. Wenn Sie nicht wüssten, was Sie tun, würden Sie wahrscheinlich Duplikate von Daten erstellen, während Sie den Vorgang durchlaufen haben.

Zumindest sehe ich das im Moment so!

Chris Barry
quelle
Nachträglich könnten Sie eine feste Speichermenge für das Speichern von Bildern / Audio / Video definieren, beispielsweise auf einem speicherbeschränkten Gerät, aber dann müssten Sie sich mit einer Art Streaming-In- und Out-Out-Lösung befassen.
Chris Barry
1

Hier als C ++ - Neuling sprechen:

Es hat eine Weile gedauert, bis das Zeigersystem nicht unbedingt aufgrund des Konzepts, sondern aufgrund der C ++ - Syntax in Bezug auf Java verarbeitet wurde. Ein paar Dinge, die ich verwirrend fand, sind:

(1) Variablendeklaration:

A a(1);

vs.

A a = A(1);

vs.

A* a = new A(1); 

und anscheinend

A a(); 

ist eine Funktionsdeklaration und keine Variablendeklaration. In anderen Sprachen gibt es grundsätzlich nur eine Möglichkeit, eine Variable zu deklarieren.

(2) Das kaufmännische Und wird auf verschiedene Arten verwendet. Wenn ja

int* i = &a;

dann ist das & a eine Speicheradresse.

OTOH, wenn es so ist

void f(int &a) {}

dann ist & a ein Referenzparameter.

Obwohl dies trivial erscheinen mag, kann es für neue Benutzer verwirrend sein - ich stamme aus Java und Java ist eine Sprache mit einer einheitlicheren Verwendung von Operatoren

(3) Array-Zeiger-Beziehung

Eine Sache, die ein bisschen frustrierend zu verstehen ist, ist ein Zeiger

int* i

kann ein Zeiger auf ein int sein

int *i = &n; // 

oder

kann ein Array zu einem int sein

int* i = new int[5];

Und nur um die Dinge chaotischer zu machen, sind Zeiger und Array nicht in allen Fällen austauschbar und Zeiger können nicht als Array-Parameter übergeben werden.

Dies fasst einige der grundlegenden Frustrationen zusammen, die ich mit C / C ++ und seinen Zeigern hatte, die IMO durch die Tatsache, dass C / C ++ all diese sprachspezifischen Macken aufweist, erheblich verstärkt wird.

Einige Neulinge
quelle
C ++ 2011 hat die Dinge in Bezug auf Variablendeklarationen erheblich verbessert.
Gnasher729
0

Ich persönlich habe den Zeiger auch nach meinem Abschluss und nach meinem ersten Job nicht verstanden. Das einzige, was ich wusste, ist, dass Sie es für verknüpfte Listen, Binärbäume und zum Übergeben von Arrays an Funktionen benötigen. Dies war die Situation schon bei meinem ersten Job. Erst als ich anfing, Interviews zu geben, verstehe ich, dass das Zeigerkonzept tiefgreifend ist und einen enormen Nutzen und Potenzial hat. Dann fing ich an, K & R zu lesen und ein eigenes Testprogramm zu schreiben. Mein ganzes Ziel war berufsorientiert.
Zu diesem Zeitpunkt stellte ich fest, dass Zeiger weder schlecht noch schwierig sind, wenn sie auf gute Weise vermittelt werden. Als ich C im Abschluss lernte, war unserem Lehrer der Zeiger leider nicht bekannt, und selbst die Aufgaben verwendeten weniger Zeiger. In der Graduiertenebene besteht die Verwendung von Zeigern eigentlich nur darin, Binärbäume und verknüpfte Listen zu erstellen. Dieser Gedanke, dass Sie kein richtiges Verständnis der Zeiger benötigen, um mit ihnen zu arbeiten, tötet die Idee, sie zu lernen.

Manoj R.
quelle
0

Zeiger .. hah .. alles über Zeiger in meinem Kopf ist, dass es eine Speicheradresse gibt, in der die tatsächlichen Werte von was auch immer seine Referenz .. also keine Magie darüber .. wenn Sie eine Baugruppe lernen, würden Sie nicht so viel Mühe haben zu lernen wie Zeiger funktionieren .. komm schon Leute ... auch in Java ist alles eine Referenz ..

Cass
quelle
0

Das Hauptproblem Menschen verstehen nicht, warum sie Zeiger brauchen. Weil sie nicht klar über Stapel und Haufen sind. Es ist gut, vom 16-Bit-Assembler für x86 mit winzigem Speichermodus zu starten. Es hat vielen Menschen geholfen, sich ein Bild von Stapel, Haufen und "Adresse" zu machen. Und Byte :) Moderne Programmierer können Ihnen manchmal nicht sagen, wie viele Bytes Sie benötigen, um 32-Bit-Speicherplatz zu adressieren. Wie können sie sich ein Bild von Zeigern machen?

Der zweite Moment ist die Notation: Sie deklarieren den Zeiger als *, Sie erhalten die Adresse als & und dies ist für manche Menschen nicht leicht zu verstehen.

Und das Letzte, was ich sah, war ein Speicherproblem: Sie verstehen Heap und Stack, können sich aber keine Vorstellung von "statisch" machen.

user996142
quelle