Warum finden alle Zeiger in c und c++ schwierig/unnötig?

3 Antworten

Pointer sind verweise auf Speicher.

In Sprachen wie Java und C# wird der Speicher für einen verwaltet. Das gilt auch für Pointer. Entweder kann man gar nicht direkt auf Speicher verweisen oder das ist nur in Ausnahmen vorgesehen.

An sich funktioniert es aber genau so, auch bei einer Referenz wird nicht der Wert übergeben sondern eben eine … Referenz.

durch das manuelle verwalten von Speicher entstehen häufig bugs, auch gerade Sicherheitsrelevante.

Was Performance kostet bei C#/Java ist vor allem auch das sichere Freigeben von Speicher.

Bei Rust hat man aber z.B. keine Garbage Collection zur Laufzeit sondern das wird direkt beim kompilieren aufgelöst. In Rust arbeitet man auch nicht direkt mit Pointern (kann man zumindest nicht einfach so dereferenzieren) aber du hast trotzdem Speichersicherheit.

Woher ich das weiß:Berufserfahrung – Software Entwickler / Devops
du übergibst eine Adresse an zB. eine Funktion, und kannst dort mithilfe der Adresse auf die Variable zugreifen

Das ist so nicht korrekt. Der Zeiger zeigt immer auf einen Speicherbereich. Weil es eine Variable ist, kannst Du darüber nur sehr einfach an den Speicherbereich kommen.

Das spart überhaupt keine Geschwindigkeit, weil es einfach in allen Programmiersprachen so ist und das ist auch nicht der Vorteil von Zeigern.

Was Du mit Pointern erreichen kannst, ist, dass Werte eben nicht by value sondern by reference übergeben werden. Sprich: Änderst Du den Speicherinhalt "Wert der Variablen" lokal, ändert er sich für alle Referenzen. Das ist bei Parametern by value anders, da diese zunächst kopiert werden.

Sie wurden in Java und C# auch nicht entfernt. In Java und C# arbeitest Du ebenfalls mit Zeigern - sie heißen nur Referenzen, tun aber dasselbe und Du musst Dich nicht um die Speicherverwaltung kümmern. Du kannst lediglich nicht den Speicherbereich durch Cast als Array von Bytes verwenden, wie Du das in C/C++ könntest. In C# kannst Du sogar Pointerarithmetik machen, Du musst es aber explizit anfordern).

Beispiel in C#:

var array = new byte[1024];

Voila - array ist eine Referenz und somit ein Zeiger auf ein Array von 1024 Bytes. Genau wie in C++.

Und das ist der Grund, warum viele Leute Zeiger umständlich und überflüssig finden: Es kann viel mehr schiefgehen und Du hast einen Haufen Verwaltungskram am Backen.

Muellmann275  08.01.2022, 12:00

Selbstverständlich kann der Aufruf einer Funktion mit einem Zeiger auf die Daten sehr viel Geschwindigkeit sparen. Und zwar umso mehr, je mehr Daten es sind. Angenommen, man hat eine sehr große Struktur:

struct SehrGross { ... }

Wenn ich nun eine Funktion habe, die so aussieht:

void f(SehrGross x) { ... }

Dann muss bei einem Funktionsaufruf der gesamte Strukturinhalt kopiert werden, was entsprechend Zeit kostet, umso mehr, je größer die Struktur ist.

Wenn ich aber eine Funktion habe wie diese:

void f(const SehrGross *p) { ... }

dann wird nur ein Zeiger auf die Struktur übergeben. Das ist unabhängig von der Größe der Struktur immer (gleich) schnell.

Insofern hat Jamie691 das mit dem Geschwindigkeitsvorteil bei einem Funktionsaufruf vollkommen korrekt beschrieben.

Kontraproduktiv ist es nur bei sehr kleinen Werten/Variablen, die übergibt man effizienter direkt, statt über einen Zeiger darauf.

0
ohwehohach  09.01.2022, 14:02
@Muellmann275
Insofern hat Jamie691 das mit dem Geschwindigkeitsvorteil bei einem Funktionsaufruf vollkommen korrekt beschrieben.

Was ich meinte ist, dass dadurch, dass in Sprachen wie Java oder den .NET Sprachen implizit ebenfalls Zeiger verwendet werden (nur halt nicht als solche gekennzeichnet, weil ohnehin bis auf primitive Typen alles Objektreferenzen sind) C++ hier keinen besonderen Geschwindigkeitsvorteil bietet, eben, weil es in anderen Sprachen implizit ebenfalls automatisch so ist.

Im Vergleich zu Sprachen wir Pascal ist es natürlich richtig, dass es einen Unterschied macht, ob eine komplette Struktur auf den Stack kopiert wird oder nur ein Zeiger - aber auch dieses Vorgehen hat durchaus seine Berechtigung, nämlich wenn vermieden werden soll, dass die Originalstruktur innerhalb der Funktion verändert werden kann.

0

Bei der Verwendung von raw pointers kann so einiges schiefgehen. Bei dynamischer Speicherallokation mit z.B. dem new Keyword kann der delete-Aufruf vergessen werden (oder eine Exception fliegt vorher) und so leaked das Programm fröhlich Speicher vor sich her. Oder du deletest versehentlich 2 mal den Speicher, wodurch du direkt im undefined Behavior bist. Ebenso weißt du bei raw pointer nicht wie viele Instanzen auf eine Speicheradresse verweisen und ob du den Bereich sicher löschen kannst, ohne das Pointer woanders hängen.

In C++ gibt es dafür seit C++11 das Konzept der smart_pointer wie unique_pointer oder shared_pointer. Raw pointer sollten ohne guten Grund vermieden werden, da in den meisten Fällen eine normale Referenz den Job auch erledigt und das genauso performant und weitaus ungefährlicher. Daher wurde sich bei Java und C# auch für die Verwendung von Referenzen entschieden.