C#, Java und Zeiger



  • Bashar schrieb:

    Nicht Zeiger sind "böse", sondern Zeigerarithmetik inklusive der Möglichkeit, einen Zeiger auf eine beliebige Speicherstelle zeigen zu lassen.

    Und wie werden dann 'NullReferenceExceptions' gerechtfertigt?
    Da zeigt der Zeiger ja auch auf eine ungueltige Speicherstelle



  • Die richtig übel zu debuggenden Probleme sind nicht die, wo der Zeiger=NULL ist, sondern die wo er *irgendwohin* zeigt. (eben nach übler Zeigerarithmetik)
    Das ist doch das, was mit dem Ansatz von C# und Java ausgeschlossen wird, oder?
    Dort gibt's nur gültige Zeiger oder eben NULL-Zeiger. Aber letztere hat man im Griff.



  • Das wesentliche an Zeigern ist, dass sie optional auch auf *nichts* zeigen können. Ohne das wär das ganze ziemlich sinnlos. Wie willst du denn zb eine verkettete Liste terminieren, oder einen Baum ...
    Das bringt es mit sich, dass es einen bestimmten Zeigerwert geben muss, der dieses nichts symbolisiert. Man könnte das einerseits so machen:

    Object __nothing;
    void * null = &__nothing;
    

    Man müsste bei jeder Dereferenzierung des Pointers überprüfen, ob er auf null zeigt. Oder man nimmt einfach die Adresse 0 dafür, und überprüft trotzdem. Oder man läßt die Überprüfung sein und erklärt den Effekt für undefiniert.

    Fakt ist: Man kommt um sowas nicht drumherum. In Lisp gibts NIL, in Eiffel gibts Void, in C# gibts void (diese drei Sprachen kennen keine expliziten Zeiger wie in C++).

    Mal ein Extrembeispiel ... in SML (funktionale Sprache) gibt es keine Zeiger (jedenfalls solange man im funktionalen Teil der Sprache bleibt). Es gibt nur Werte. Einen optionalen Wert stellt man mit einem OPTION-Typ dar (das ist ein polymorpher Typ, in etwa zu vergleichen mit C++-Templates ...):

    datatype 'a OPTION = NONE | SOME of 'a
    -- sei foo eine Funktion, die optional ein int zurückliefert
    -- dh vom Ergebnistyp int OPTION
    case foo () of
      SOME n => machwas mit n
    | NONE => kein Ergebnis behandeln
    

    (mein SML ist leicht rostig, mag sein dass die syntax irgendwo mal nicht ganz hinhaut)

    Sorry für das weite Ausholen, aber was glaubst du passiert, wenn ich in dem case den NONE-Zweig weglasse, und foo ein NONE zurückliefert? Eine Exception! Es geht schlicht nicht anders, die Optionalität bringt das mit sich. Der Unterschied zu C++ ist, dass man Exceptions abfangen kann, UB nicht.



  • Soweit ist mir der kleine Vorteil auch klar. Doch warum macht man nicht einen großen Vorteil und sagt:

    Jede Referenz zeigt auf ein Objekt. dh dass nicht NULL der Default ist, sondern dass ich, wenn ich

    Klasse obj;
    

    schreibe, wie in C++ der Standard Ctor aufgerufen wird.

    Das würde bedeuten: es gibt keine NULL-Zeiger, bzw. es gibt nur dann NULL-Zeiger wenn ich es explizit will:

    public static void foo(Klasse obj)
    {
    }
    //...
    foo(nil);
    

    Damit würden doch schon die meisten NullReferenceExceptions wegfallen.
    Und man würde das C Problem der uninitialisierten Variablen umgehen.

    Denn wenn diese Referenzen keine echten Zeiger sind, kann ich mir das explizite Speicherallokieren (obj=new Klasse();) sparen.

    Vielleicht bin ich blind und blöd, aber ich sehe den Vorteil von den ganzen Null-Referenzen nicht.

    Sicher, eine Null-Referenz ist besser als ein wilder Zeiger, aber was hindert die Sprachdesigner daran, generell keine uninitialisierten Referenzen zuzulassen, bzw. es explizit zu fordern wenn es denn unbedingt sein muss:

    Klasse foo; //ruft Ctor auf, bzw. ist verboten
    Klasse foo2=nil; //ruft keinen Ctor auf, sondern NULLt die Referenz
    Klasse foo3=new Klasse(); //ruft Ctor auf, ob foo3 oder foo besser ist, ist egal
    

    Vielleicht programmiere ich zu viel C++, aber in C# sind 70% meiner Fehler das Vergessen einer Initialisierung.



  • Wohin zeigt ein Zeiger wenn Du ihn in C++ anlegst? Genau irgendwohin, in Java ist das eine Nullreferenz, automatisch, das heißt Du kannst zumindest problemlos abfragen ob sich da ein gültiges Objekt dahinter verbirgt oder nicht.

    Der Rest ist doch reine Syntax:
    C++:

    Klasse obj;
    

    wird in Java zu:

    Klasse obj = new Klasse();
    

    Das ist weder ein Vor- noch ein Nachteil, das ist schlicht und einfach ein popliger syntaktischer Unterschied.

    Und mit

    Klasse obj;
    implizit das new durchzuführen halte ich nicht für allzugeschickt. Das sieht so harmlos aus und kann doch so teuer sein.



  • Shade Of Mine schrieb:

    Klasse obj;
    

    schreibe, wie in C++ der Standard Ctor aufgerufen wird.

    Das heißt jedesmal still und heimlich ein new?

    Das würde bedeuten: es gibt keine NULL-Zeiger, bzw. es gibt nur dann NULL-Zeiger wenn ich es explizit will

    OK, wär eine Möglichkeit. Wenn ich dich richtig interpretiere, willst du, dass Referenzen in Java/C# nicht automatisch mit null/void initialisiert werden, sondern mit frisch erzeugten Objekten.

    Und wozu? -->

    Damit würden doch schon die meisten NullReferenceExceptions wegfallen.
    Und man würde das C Problem der uninitialisierten Variablen umgehen.

    Also nur um Anfängerfehler zu vermeiden?

    Denn wenn diese Referenzen keine echten Zeiger sind, kann ich mir das explizite Speicherallokieren (obj=new Klasse();) sparen.

    Was würde das bringen? Explizites new ist doch gut, damit man sieht, wo Objekte erzeugt werden.

    Vielleicht programmiere ich zu viel C++, aber in C# sind 70% meiner Fehler das Vergessen einer Initialisierung.

    Anfängerfehler.



  • @Shade:
    Keine Ahnung, ob du zuviel C++ programmierst - geht das überhaupt? *gg*
    Jedenfalls ist, was dieses Thema angeht, C++ semantisch viel sauberer als Java. Der Ausdruck:

    T obj;
    

    Legt in C++ immer ein Objekt vom Typ T an, wobei ggf. Konstruktoren aufgerufen werden. In Java wird, falls T ein eingebauter Datentyp (z.B. int) ist, genauso verfahren. Ist T allerdings ein selbstdefinierter Typ, dann handelt es sich bei T um einen Pointer und der ist standardmäßig Null. Als C++-Programmierer ist man dieses semantische Kuddelmuddel einfach nicht gewohnt.

    Dein Vorschlag, daß, sollte T ein selbstdefinierter Typ sein der Pointer implizit mit einem neuen Objekt initialsiert wird, ist nicht durchführbar, da auch Java-Programmierer den Nullstatus von Pointern brauchen. Es muß z.B. möglich sein, Konstrukte wie

    // ...
    if ( obj == nil)  // ist das korrektes Java?
       {
       // ...
       }
    // ...
    

    zu verwenden, denke ich. Und erstrecht braucht man das bei der Definition von Klassen:

    class MyClass {
       T obj;
    };
    

    Ich glaube, die Sprache wäre nahezu unbrauchbar, wenn hier obj immer gleich mit einem geeigneten Objekt initialisiert würde.

    Stefan.



  • Implizites Anlegen finde ich auch nicht sonderlich elegant. Ich wäre wenn überhaupt dafür, dass man gezwungen wird eine Referenz zu initialisieren.

    Und der Vortiel vn Referenzen gegenüber Zeigern ist u.A. auch, dass man Sie nicht explizit dereferenzieren muss.



  • 1. Solche Nicht-Initialisierungen können bei Java zumindest nur sehr eingeschränkt auftreten. Das geht nur bei Membervariablen und bei Arrayelementen. Bei lokalen Variablen, wie bei

    import javax.swing.*;
    
    public class InitTest
    {
       public static void main (String [] args)
       {
          JFrame frame;
          frame.setVisible(true);
       }
    }
    

    kriegt man beim Kompilieren bereits folgenden Fehler:

    InitTest.java:8: variable frame might not have been initialized
          frame.setVisible(true);
          ^
    1 error
    

    2. Eine automatische Initialisierung führt IMHO zu Fehlern, die schwerer aufzufinden sind als simple NullPointerExceptions bei nichtinitialisierten Membervariablen. Diese Fehler sind schließlich dadurch gekennzeichnet, dass keine Fehlermeldung kommt, sondern der Code einfach nicht so funktioniert, wie er soll. Meistens will man so eine Variable schließlich nicht mit irgendeinem Standardobjekt initialisieren, sondern mit etwas speziellem.

    3. Eine automatische Initialisierung kostet Zeit und Speicher. Letztendlich muss man das richtige Objekt ja doch erzeugen und der Variable zuweisen. Der Rechner arbeitet dann also doppelt.



  • Ich schliesse mich Helium an

    gezwungene initialisierung von zeigern wie in java ist
    wesentlich besser fuer den programmierstil

    ausserdem erzaehlt dir java bei einer uninitialisierten referenz immer das da was nicht stimmen kann

    mach mal

    JPanel pan;
    

    der compiler verlangt zumindest

    JPanel pan = null;
    

    ich finde diese schreibweise besser und mache das auch in C++ immer so

    Ausserdem ist es gut fuer anfaengernur referenzen zu haben weil wieviele

    void function (Klasse Objekt)
    

    hab ich schon gesehen wo immer der copy constructor aufgerufen wird

    ich bin zB ein extremer Referenzen Nutzer in C++
    und benutze eigentlich fast nie zeiger - ausser wenn ich wirklich zeigerarithmetik brauche

    C:
    schlimm ist es wenn man da mal einen * vergisst
    dann springt man schon wie wild im speicher - das geht bei Java nicht

    auf der anderen seite ist das auch ein nachteil von java - man kann nicht wie wild im speicher herumhuepfen

    gomberl

    Nachtrag:
    @DStefan:
    wer sagt den das T ein objekt ist?
    kann ja genauso fuer int stehen - in C++ laesst sich ja viel mit den präprozessor anweisungen machen - der praeprozessor ist auch eines der besten dinge in C++ - was man da alles machen kann



  • gomberl schrieb:

    der praeprozessor ist auch eines der besten dinge in C++ - was man da alles machen kann

    Meinst du das ernst?! 😮 😮 😮



  • DStefan schrieb:

    Jedenfalls ist, was dieses Thema angeht, C++ semantisch viel sauberer als Java. Der Ausdruck:

    T obj;
    

    Legt in C++ immer ein Objekt vom Typ T an, wobei ggf. Konstruktoren aufgerufen werden. In Java wird, falls T ein eingebauter Datentyp (z.B. int) ist, genauso verfahren. Ist T allerdings ein selbstdefinierter Typ, dann handelt es sich bei T um einen Pointer und der ist standardmäßig Null. Als C++-Programmierer ist man dieses semantische Kuddelmuddel einfach nicht gewohnt.

    Genau umgekehrt. In Java ist das semantisch viel sauberer.
    T name;
    deklariert in Java immer eine Variable ohne ihr einen Wert zuzuweisen. Java verhält sich da bei primitiven Datentypen wie int oder double ganz genauso wie bei Variablen, die eine Objektreferenz enthalten.
    Das Kuddelmuddel hat man in C++. Denn dort wird z.B. bei einem Objekt automatisch ein neues Objekt erstellt und die Adresse der Variablen zugewiesen. Bei int oder double dagegen wird kein Objekt erzeugt sondern nur die Variable deklariert.

    Nochmal zur Klarstellung: Eine Variable kann niemals ein Objekt enthalten sondern immer nur eine Referenz oder einen Zeiger auf ein Objekt. Ein int dagegen ist kein Objekt und kann direkt in einer Variablen enthalten sein.



  • DrZoidberg schrieb:

    Genau umgekehrt. In Java ist das semantisch viel sauberer.
    T name;
    deklariert in Java immer eine Variable ohne ihr einen Wert zuzuweisen.

    Aber nur, wenn du 0 nicht als Wert anerkennst.

    Ich glaube die beiden Sprachen nehmen sich da nicht viel, es existiert in beiden Sprachen eine mehr oder weniger scharfe Trennung zwischen Variablen, die initialisiert werden müssen, und solchen, bei denen das unnötig ist bzw. automatisch passiert. In Java zwischen primitiven Typen und Referenztypen, in C++ zwischen POD und nicht-POD-Typen. Ironischerweise initialisieren sich in C++ die "höheren" Typen selbst, während das in Java die "niederen" tun 😉



  • Bashar schrieb:

    Aber nur, wenn du 0 nicht als Wert anerkennst.

    Das gilt auch wieder nur eingeschränkt auf Membervariablen und Arrayelemente:

    import javax.swing.*;
    
    public class InitTest
    {
       public static void main (String [] args)
       {
          int i;
          ++i;
       }
    }
    

    läßt sich z.B. nicht kompilieren, weil:

    InitTest.java:8: variable i might not have been initialized
          ++i;
            ^
    1 error
    

    Was man aber bei Java sagen kann, ist, dass eigentlich jede Variable initialisiert wird. Entweder automatisch mit null oder 0 oder manuell durch den Programmierer. Man kann kein Javaprogramm kompilieren, bei dem dies nicht der Fall ist.



  • DrZoidberg schrieb:

    Das Kuddelmuddel hat man in C++. Denn dort wird z.B. bei einem Objekt automatisch ein neues Objekt erstellt und die Adresse der Variablen zugewiesen. Bei int oder double dagegen wird kein Objekt erzeugt sondern nur die Variable deklariert.

    ich bin nicht einverstanden mit der trennung von objekt und variable, es gibt zwar einige implementierungs unterschiede aber auf der semantischen ebene fühlen sie sich gleich an

    DrZoidberg schrieb:

    Nochmal zur Klarstellung: Eine Variable kann niemals ein Objekt enthalten sondern immer nur eine Referenz oder einen Zeiger auf ein Objekt. Ein int dagegen ist kein Objekt und kann direkt in einer Variablen enthalten sein.

    quelle?



  • @Gregor: das ist am besten auszudruecken mit "gemischten gefuellen"

    ich komme urspruenglich aus der C und dann C++ Ecke und habe dort schon einige sehr intelligente dinge gesehen die durch den Praeprozessor gemacht wurden

    das das ganze nicht so intuitiv ist wie java ist schon klar

    im endeffekt werd ich wohl sentimental wenn ich an ihn denke, aber richtig eingesetzt ist der C++ praeprozessor schon was feines



  • RHBaum: Score -2, Offtopic, Troll



  • DrZoidberg schrieb:

    Nochmal zur Klarstellung: Eine Variable kann niemals ein Objekt enthalten sondern immer nur eine Referenz oder einen Zeiger auf ein Objekt. Ein int dagegen ist kein Objekt und kann direkt in einer Variablen enthalten sein.

    Genau das ist der semantische Kuddelmuddel, diese völlig unnatürliche Unterscheidung zwischen eingebauten und selbstdefinierten Datentypen. Warum sagst du, ein int sei kein Objekt? Natürlich ist es eins! Wenn auch kein Objekt im Sinne von "Instanz einer Klasse". Und diese Unterscheidung ist absolut nicht einzusehen. C++ bemüht sich (wie ich finde mit Erfolg), möglichst keinen Unterschied zwischen den Datentypen zu machen.

    Die Konsequenzen sieht man auch an folgendem:

    T eins;
    T zwei;
    // ..... In Java: Initialisiere eins und zwei entsprechend ....
    eins = zwei;
    // Irgend eine geeignete Änderungn von zwei.
    // Welchen Wert hat eins, welchen zwei?!
    

    In Java macht es für diesen Code einen Unterschied, ob T ein eingebauter oder ein Klassentyp ist. Gilt ersteres, dann ist "eins = zwei" eine Wertzuweisung, gilt letzteres, werden aber Pointer, nicht Werte zugewiesen: eins zeigt nach der Zuweisung auf dasselbe Objekt wie zwei.

    Folglich ist nach der "geeigneten Änderung" der Wert von eins und zwei verschieden, falls T ein eingebauter Datentyp ist, bei selbstdefinierten ist er aber gleich, da beide Pointer ja auf dasselbe Objekt zeigen.

    Das nenne ich einen semantischen Kuddelmuddel.

    Stefan.



  • ich finde es eigentlich wesentlich besser in java als in C++, weil du eine strikte trennung zwischen simplen datentypen und klassen hast

    was IMHO einfach besser lesbar ist und du weisst was du tust

    ausserdem kannst du ja auch kein objekt einer variablen zuweisen
    also was will ich a = b machen ?

    was voellig anders waere es wenn sie die einfachen datentypen auch als klassen definiert haetten - was besser waere im sinne der OOP

    dadurch mixt man auch weniger oo ansichten mit prozeduraler programmierung

    ich hoffe ihr wisst was ich meine



  • gomberl: C++ ist keine Sprache, die eine strikte Trennung zwischen Klassen und primitiven Typen will, noch ist es eine, die für jede Variable den Overhead einer Heap-Allokation will. Klassen ergeben sich nahtlos aus "dummen" Strukturen, indem ich Memberfunktionen hinzufüge, Zugriffsrechte vergebe, und grundlegende Vorgänge wie Konstruktion/Destruktion, Kopieren und Zuweisung anpasse. Ich baue tatsächlich neue Datentypen. Nimm z.B. eine Klasse für komplexe Zahlen. Das ist schlicht und einfach eine Erweiterung der vorhandenen Datentypen, kein Objekt im Sinne der OOP, und in C++ trivial zu bauen. In Java bastelt man sich an der Stelle per Hand eine Wertsemantik (value types) zurecht (alle Felder final, keine Modifikationsmöglichkeit ...), wie bei den Strings.
    Dafür ist in C++ die Benutzung von entity types, die in der OOP häufiger vorkommen, etwas umständlicher, die man sich selbst aus den gegebenen Mitteln schneidern muss (Kopierkonstruktor und Zuweisung private machen z.B.). Aber C++ will auch keine reine OOP-Sprache sein. Es gibt kein alleinseligmachendes Paradigma(TM), und wenn, heißt es nicht OO.


Anmelden zum Antworten