Erklären Sie “C hat grundsätzlich ein beschädigtes Typsystem”

Lesezeit: 11 Minuten

Benutzer-Avatar
Ian Mackinnon

Im Buch Programmierer bei der Arbeit (p355) sagt Guy Steele über C++:

Ich denke, die Entscheidung, mit C abwärtskompatibel zu sein, ist ein fataler Fehler. Es ist nur eine Reihe von Schwierigkeiten, die nicht überwunden werden können. C hat grundsätzlich ein korruptes Typsystem. Es ist gut genug, um Ihnen zu helfen, einige Schwierigkeiten zu vermeiden, aber es ist nicht luftdicht und Sie können sich nicht darauf verlassen

Was meint er damit, das Typensystem als „korrupt“ zu bezeichnen?

Können Sie das anhand eines einfachen Beispiels in C demonstrieren?

Bearbeiten:

  1. Das Zitat klingt polemisch, aber das versuche ich nicht. Ich möchte einfach verstehen, was er meint.

  2. Bitte geben Sie Beispiele an C nicht C++. Ich interessiere mich auch für den “grundlegenden” Teil 🙂

  • Oh je, jetzt hast du es geschafft.

    – Tim Post

    8. November 2010 um 14:42 Uhr

  • Ich bin überrascht, dass die Leute das geschlossen haben. Das zitieren ist “subjektiv und argumentativ”, aber der Versuch zu verstehen, was es bedeutet, ist weitgehend objektiv.

    – Gülle

    8. November 2010 um 15:09 Uhr

  • @Manur: Das Problem ist, wie der enge Grund sagt, dass Fragen dieser Art “normalerweise zu Konfrontation und Streit führen”. Was hier wohl zutrifft.

    – jalf

    8. November 2010 um 15:17 Uhr

  • @Ian: Guy will natürlich provozieren. Er ist der Meinung, dass Typensysteme alles Schlimme verhindern sollten und dass sie grundsätzlich ein Garant für Sicherheit sind. C-Programmierer denken nicht so.

    – Donal Fellows

    8. November 2010 um 16:46 Uhr

  • C hat ein Typensystem?

    – bmargulies

    9. November 2010 um 0:31 Uhr

Benutzer-Avatar
Goldesel

Die offensichtlichen Beispiele in C für Nichttypsicherheit ergeben sich einfach aus der Tatsache, dass Sie void * in jeden Typ umwandeln können, ohne dies explizit umwandeln zu müssen.

struct X
{
  int x;
};

struct Y
{
  double y;
};

struct X xx;
xx.x = 1;
void * vv = &xx;
struct Y * yy = vv; /* no need to cast explicitly */
printf( "%f", yy->y );

Natürlich ist printf selbst nicht genau typsicher.

C++ ist nicht vollständig typsicher.

struct Base
{
   int b;
};

struct Derived : Base
{
  int d;

  Derived() 
  {
     b = 1;
     d = 3;
  }
};

Derived derivs[50];
Base * bb = &derivs[0];
std::cout << bb[3].b << std::endl;

Es ist kein Problem, das Derived* in eine Base* umzuwandeln, aber Sie stoßen auf Probleme, wenn Sie versuchen, die Base* als Array zu verwenden, da die Zeigerarithmetik völlig falsch ist und obwohl alle b-Werte 1 sind, erhalten Sie möglicherweise eine 3 (Da die Ints 1-3-1-3 usw. gehen)

  • Java sollte Sie das auch nicht zulassen, aber es tut es und schafft seine eigenen Probleme.

    – Ken Bloom

    8. November 2010 um 14:53 Uhr

  • @Ken: Was Java erlaubt, ist etwas anders. Zum einen kann man in Java nicht einmal ein solches Array erstellen, am nächsten kommt man dem C-Code Derived* derivs[50];und DAS ist in C++ nicht defekt (Sie können es nicht austauschbar behandeln mit Base* [50]).

    – Ben Voigt

    8. November 2010 um 16:10 Uhr

  • @CashCow, nur um wählerisch zu sein. In C kann man nicht casten void* zu jedem anderen Zeigertyp, sondern nur zu Zeigern auf Daten. Es ist nicht erlaubt, auf Funktionszeiger umzuwandeln.

    – Jens Gustedt

    8. November 2010 um 17:29 Uhr

  • Ich sehe das nicht als Schwäche. void * sind nicht besonders nützlich, wenn Sie sie nicht auf etwas übertragen. Es ist ein echtes PITA in C++, alles nach links rechts und in die Mitte zu werfen.

    – Matt Tischler

    8. November 2010 um 21:55 Uhr


  • @Matt Joiner: Wenn Sie in C++ “alles nach links und rechts umwandeln müssen”, machen Sie etwas falsch

    – Niki

    8. November 2010 um 23:14 Uhr

Benutzer-Avatar
scharfer Zahn

Grundsätzlich können Sie jeden Datentyp in jeden Datentyp umwandeln

struct SomeStruct {
    void* data;
};

struct SomeStruct object;
*( (int*) &object ) = 10;

und niemand fängt dich.

  • Das ist nicht C, außerdem schaltet das Casting das Typsystem aus, es ist kein Beweis für die Unzulänglichkeit des Typsystems.

    – Ben Voigt

    8. November 2010 um 14:47 Uhr

  • Ja, das sieht nach C++ aus. Können Sie ein Beispiel dafür in C zeigen?

    – Ian Mackinnon

    8. November 2010 um 14:51 Uhr

  • @ Ben: Ja, das ist es. Das Typsystem steuert, welche Umwandlungen zulässig sind. Und eine Umwandlung, die es Ihnen erlaubt, das Typsystem zu ignorieren, ist ein ziemlich guter Hinweis darauf, dass das Typsystem kaputt ist (obwohl dies sicherlich nicht bedeutet, dass die Sprache ist kaputt. Oder sogar, dass das Typensystem nutzlos ist)

    – jalf

    8. November 2010 um 15:12 Uhr

  • @jalf: Häh? Es gibt keinerlei Einschränkungen für Umwandlungen (möglicherweise benötigen Sie einen Adressoperator oder zwei Umwandlungen hintereinander, aber ich denke, Sie können von jedem Typ zu jedem anderen Typ gelangen). In C schaltet das Casting das Typsystem aus. Die Tatsache, dass andere Sprachen einen Typumwandlungsoperator definieren, der wie eine Typumwandlung in C aussieht, ändert daran nichts.

    – Ben Voigt

    8. November 2010 um 15:45 Uhr

  • @Ben: Sie können von jedem Typ zu jedem anderen Typ wechseln, ohne dass das Typsystem Sie erwischt, sicher, aber dies ist in vielen Fällen ein undefiniertes Verhalten. (Denken Sie an die Aliasing-Regeln: Sie dürfen keine an int* zu einem float*der Compiler kann Sie normalerweise nur nicht daran hindern.)

    – jalf

    8. November 2010 um 16:37 Uhr

char buffer[42];
FunctionThatDestroysTheStack(buffer);  // By writing 43 chars or more

  • Ich bin mir nicht sicher, ob das ein Fehler im Typensystem ist.

    – Oliver Charlesworth

    8. November 2010 um 14:49 Uhr

  • Ein gutes Beispiel, aber jede Sprache, die direkten uneingeschränkten Speicherzugriff unterstützt, ermöglicht es Ihnen, einfach schrecklichen Code zu schreiben, und alle bieten gut verständliche Programmiermuster, um diese Fallstricke zu vermeiden. Schlechter Programmierer != schlechte Sprache.

    – Steve Townsend

    8. November 2010 um 14:50 Uhr

  • @KenBloom: Uneingeschränkter Speicherzugriff hat nichts mit dem Typsystem zu tun. (z. B. PEEK und POKE akzeptieren nur ganzzahlige Argumente)

    – kennytm

    8. November 2010 um 14:58 Uhr

  • @Ken: Der C++-Standard hat dafür einen anderen Begriff, typischerweise “implementierungsabhängig”.

    – Ben Voigt

    8. November 2010 um 16:05 Uhr

  • @Matt – stimmt nicht ganz. Anständige JIT-Compiler können den Code gut genug analysieren, um zu sehen, dass eine Schleife ein Array nicht außerhalb der Grenzen indiziert, und die Indexprüfung auslassen. Sicher und schnell sind unvereinbare Ziele.

    – Hans Passant

    8. November 2010 um 22:11 Uhr

Benutzer-Avatar
Ben Voigt

Das System vom Typ C hat einige Probleme. Dinge wie implizite Funktionsdeklaration und implizite Konvertierung von void* kann die Typsicherheit lautlos brechen.

C++ behebt so ziemlich alle diese Lücken. Das C++-Typsystem ist NICHT abwärtskompatibel mit C, es ist nur mit gut geschriebenem typsicherem C-Code kompatibel.

Darüber hinaus verweisen die Leute, die gegen C++ argumentieren, normalerweise auf Java oder C# als “Lösung”. Dennoch haben Java und C# Löcher in ihrem Typsystem (Array-Kovarianz). C++ hat dieses Problem nicht.

BEARBEITEN: Beispiele in C++, die versuchen, Array-Kovarianz zu verwenden, die (unsachgemäß) von den Typsystemen Java und C# zugelassen würde.

#include <stdlib.h>

struct Base {};
struct Derived : Base {};

template<size_t N>
void func1( Base (&array)[N] );

void func2( Base** pArray );

void func3( Base*& refArray );

void test1( void )
{
  Base b[40];
  Derived d[40];

  func1(b); // ok
  func1(d); // error caught by C++ type system
}

void test2( void )
{
  Base* b[40] = {};
  Derived* d[40] = {};

  func2(b); // ok
  func2(d); // error caught by C++ type system

  func3(b[0]); // ok
  func3(d[0]); // error caught by C++ type system
}

Ergebnisse:

Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing.  All rights reserved.
MODE:strict errors C++ C++0x_extensions

"ComeauTest.c", line 19: error: no instance of function template "func1" matches
          the argument list
            The argument types that you used are: (Derived [40])
        func1(d); // error caught by C++ type system
        ^

"ComeauTest.c", line 28: error: argument of type "Derived **" is incompatible with
          parameter of type "Base **"
        func2(d); // error caught by C++ type system
              ^

"ComeauTest.c", line 31: error: a reference of type "Base *&" (not const-qualified)
          cannot be initialized with a value of type "Derived *"
        func3(d[0]); // error caught by C++ type system
              ^

3 errors detected in the compilation of "ComeauTest.c".

Das bedeutet nicht, dass es überhaupt keine Lücken im C++-Typsystem gibt, aber es zeigt, dass Sie einen Pointer-to-Derived nicht stillschweigend mit einem Pointer-to-Base überschreiben können, wie es Java und C# erlauben.

Sie müssten ihn fragen, was er meinte, um eine endgültige Antwort zu erhalten, oder vielleicht mehr Kontext für dieses Zitat liefern.

Es ist jedoch ziemlich klar, dass, wenn dies ein fataler Fehler für C++ ist, die Krankheit chronisch und nicht akut ist – C++ gedeiht und entwickelt sich kontinuierlich weiter, wie die laufenden Bemühungen um Boost und C++0x zeigen.

Ich denke nicht einmal mehr an C und C++ als gekoppelt – ein paar Wochen auf dem jeweiligen Für ein hier heilt man schnell von jeglicher Verwirrung darüber, dass es sich um zwei verschiedene Sprachen handelt, jede mit ihren eigenen Stärken und Schwächen.

  • +1 für “Schwächen”. Fast tolkenesk und gab mir Bilder von malerischen Dörfern und Bierfahnen.

    – Muh-Saft

    8. November 2010 um 14:46 Uhr

  • Ich wollte keinen Krieg beginnen, indem ich „Schwächen“ sage.

    – Steve Townsend

    8. November 2010 um 14:51 Uhr

  • Ich denke, „Redewendungen“ ist die am wenigsten emotionale Option

    – Steve Townsend

    8. November 2010 um 15:08 Uhr

  • @Frustriert: Ja, aber man würde wirklich denken, dass sie mehr auf COBOL stehen. Alle bis auf ein paar. Die Takes könnten in C einsteigen, die Brandybucks könnten bei Bedarf sogar ein wenig Python machen, aber nur Bilbo und Frodo und ihre Freunde würden es mit objektorientiert oder funktional versuchen.

    – David Thornley

    8. November 2010 um 20:27 Uhr

  • @Moo-Juice: Abgesehen davon, dass malerische Dörfer und Bierkisten nur der Anfang und das Ende von LOTR sind. Vielleicht ist dies ein Hinweis, die meisten Leute erforschen nie weiter als die ersten paar Kapitel von C, bevor sie frustriert sind über das Chaos, das sie produzieren, ohne dass jemand (eine anmaßende Sprache) ihnen sagt, sie sollen es geheim halten, es sicher aufbewahren.

    – Matt Tischler

    8. November 2010 um 21:59 Uhr


Benutzer-Avatar
Niki

IMHO ist der “kaputtste” Teil des C-Typ-Systems, dass die Konzepte von

  • Werte/Parameter, die optional sind
  • veränderliche Werte/Pass-by-Referenz
  • Arrays
  • Nicht-POD-Funktionsparameter

werden alle auf das einsprachige Konzept “Zeiger” abgebildet. Das heißt, wenn Sie einen Funktionsparameter vom Typ erhalten X*es könnte ein optionaler Parameter sein, könnte erwartet werden, dass die Funktion den Wert ändert, auf den von zeigt X*kann es sein, dass es mehrere Instanzen von gibt X nach dem einen, auf den gezeigt wird (es ist offen, wie viele – die Zahl könnte als separater Parameter übergeben werden, oder ein besonderer “Terminator” -Wert könnte das Ende des Arrays markieren, wie in nullterminierten Zeichenfolgen). Oder der Parameter kann einfach eine einzelne Struktur sein, die Sie voraussichtlich nicht ändern, aber es ist billiger, sie als Referenz zu übergeben.

Wenn Sie etwas von der Art bekommen X**, es kann sich um ein Array optionaler Werte handeln, oder es kann sich um ein Array einfacher Werte handeln, und es wird erwartet, dass Sie es ändern. Oder es könnte ein zweidimensionales gezacktes Array sein. Oder ein optionaler Wert, der als Referenz übergeben wird.

Nehmen Sie im Gegensatz dazu die ML-Sprachfamilie (F#, OCaML, SML). Hier werden diese Konzepte auf separate Sprachkonstrukte abgebildet:

  • optionale Werte haben den Typ X option
  • Werte, die änderbar sind/als Referenz übergeben werden, haben den Typ X ref
  • Arrays haben den Typ X array
  • und Nicht-POD-Typen können wie PODs übergeben werden. Da sie nicht änderbar sind, kann der Compiler sie intern als Referenz übergeben, aber Sie müssen nichts über diese Implementierungsdetails wissen

Und Sie können diese natürlich kombinieren, dh int optional ref ist ein veränderlicher Wert, der auf nichts oder einen ganzzahligen Wert gesetzt werden kann. int ref optional andererseits ist ein optionaler veränderlicher Wert; es kann nichts sein (und niemand kann es ändern) oder es kann ein änderbares int sein (und Sie können es in jedes andere änderbare it ändern, aber nicht nichts).

Diese Unterscheidungen sind sehr subtil, aber Sie müssen sie machen, ob Sie in ML programmieren oder nicht. In C müssen Sie die gleichen Unterscheidungen treffen, aber sie werden im Typsystem nicht explizit angegeben. Sie müssen die Dokumentation sehr sorgfältig lesen, oder Sie könnten subtile (sprich: schwer zu findende) Fehler einführen, wenn Sie missverstehen, welche Art von Zeigerverwendung wann gemeint ist.

  • +1 für “Schwächen”. Fast tolkenesk und gab mir Bilder von malerischen Dörfern und Bierfahnen.

    – Muh-Saft

    8. November 2010 um 14:46 Uhr

  • Ich wollte keinen Krieg beginnen, indem ich „Schwächen“ sage.

    – Steve Townsend

    8. November 2010 um 14:51 Uhr

  • Ich denke, „Redewendungen“ ist die am wenigsten emotionale Option

    – Steve Townsend

    8. November 2010 um 15:08 Uhr

  • @Frustriert: Ja, aber man würde wirklich denken, dass sie mehr auf COBOL stehen. Alle bis auf ein paar. Die Takes könnten in C einsteigen, die Brandybucks könnten bei Bedarf sogar ein wenig Python machen, aber nur Bilbo und Frodo und ihre Freunde würden es mit objektorientiert oder funktional versuchen.

    – David Thornley

    8. November 2010 um 20:27 Uhr

  • @Moo-Juice: Abgesehen davon, dass malerische Dörfer und Bierkisten nur der Anfang und das Ende von LOTR sind. Vielleicht ist dies ein Hinweis, die meisten Leute erforschen nie weiter als die ersten paar Kapitel von C, bevor sie frustriert sind über das Chaos, das sie produzieren, ohne dass jemand (eine anmaßende Sprache) ihnen sagt, sie sollen es geheim halten, es sicher aufbewahren.

    – Matt Tischler

    8. November 2010 um 21:59 Uhr


Benutzer-Avatar
Frankie

Hier bedeutet „korrupt“, dass es nicht „streng“ ist, was zu nie endender Freude in C++ führt (aufgrund der vielen benutzerdefinierten Typen (Objekte) und überladenen Operatoren wird Casting in C++ zu einem großen Ärgernis).

Der Angriff gegen C erfolgt im Hinblick auf seine MISPLACED USAGE als strenge OOP-Basis.

C wurde nie entwickelt, um Programmierer einzuschränken, daher vielleicht die Frustration der Wissenschaft (und die extravagante Pracht des ++, das BS der Welt gegeben hat).

“Ich habe den Begriff objektorientiert erfunden, und ich kann Ihnen sagen, dass ich C++ nicht im Sinn hatte”

(Alan Kay)

1323050cookie-checkErklären Sie “C hat grundsätzlich ein beschädigtes Typsystem”

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy