Warum verfolgen C-Arrays nicht ihre Länge?


77

Was war der Grund dafür, dass die Länge eines Arrays nicht explizit mit einem Array in gespeichert wurde C?

So wie ich das sehe, gibt es überwältigende Gründe dafür, aber nicht sehr viele, die den Standard (C89) unterstützen. Zum Beispiel:

  1. Wenn Länge in einem Puffer verfügbar ist, kann ein Pufferüberlauf verhindert werden.
  2. Ein Java-Stil arr.lengthist klar und vermeidet, dass der Programmierer viele ints auf dem Stapel halten muss, wenn er mit mehreren Arrays arbeitet
  3. Funktionsparameter werden zwingender.

Aber der vielleicht motivierendste Grund ist meiner Meinung nach, dass normalerweise kein Platz gespart wird, ohne die Länge beizubehalten. Ich würde sagen, dass die meisten Verwendungen von Arrays eine dynamische Zuordnung beinhalten. Zwar kann es vorkommen, dass Benutzer ein Array verwenden, das auf dem Stack zugewiesen ist, dies ist jedoch nur ein Funktionsaufruf * - der Stack kann 4 oder 8 Byte mehr verarbeiten.

Da der Heap-Manager ohnehin die von dem dynamisch zugewiesenen Array verbrauchte freie Blockgröße nachverfolgen muss, sollten Sie diese Informationen nutzbar machen (und die zusätzliche Regel hinzufügen, die beim Kompilieren überprüft wurde, dass die Länge nur dann explizit geändert werden kann, wenn dies der Fall ist schießen sich gern in den Fuß).

Das Einzige, woran ich auf der anderen Seite denken kann, ist, dass keine Längenverfolgung Compiler einfacher gemacht hat, aber nicht so viel einfacher.

* Technisch könnte man mit einem Array mit automatischem Speicher eine Art rekursive Funktion schreiben, und in diesem (sehr aufwändigen) Fall kann das Speichern der Länge tatsächlich zu einer effektiveren Nutzung des Speicherplatzes führen.


6
Ich nehme an, es könnte argumentiert werden, dass C, wenn es Strukturen als Parameter und Rückgabewerttypen verwendet, syntaktischen Zucker für "Vektoren" (oder welchen Namen auch immer) enthalten sollte, der darunter Struktur mit Länge und entweder Array oder Zeiger auf Array sein würde . Die Sprachunterstützung für dieses gemeinsame Konstrukt (auch wenn es als separate Argumente und nicht als einzelne Struktur übergeben wird) hätte unzählige Fehler und eine vereinfachte Standardbibliothek gespart.
Hyde

3
Vielleicht finden Sie auch, warum Pascal nicht meine bevorzugte Programmiersprache ist, Abschnitt 2.1, aufschlussreich.

34
Während alle anderen Antworten einige interessante Punkte haben, denke ich, dass C so geschrieben wurde, dass Assembler-Programmierer Code einfacher schreiben und portabel machen können. In Anbetracht dessen wäre es ein Ärgernis und kein Nachteil gewesen, wenn eine Array-Länge MIT einem Array automatisch gespeichert worden wäre (wie es auch andere nette Wünsche nach einem Bonbonüberzug gewesen wären). Heutzutage scheinen diese Funktionen nett zu sein, aber damals war es wirklich oft schwierig, ein weiteres Byte Programm oder Daten in Ihr System zu bekommen. Die verschwenderische Verwendung von Speicher hätte die Akzeptanz von C stark eingeschränkt.
Dunk

6
Der reale Teil Ihrer Antwort wurde bereits viele Male so beantwortet, wie ich es gerne getan hätte, aber ich kann einen anderen Punkt herausgreifen: "Warum kann die Größe eines malloc()ed-Bereichs nicht auf tragbare Weise abgefragt werden?" Das ist eine Sache, die mich mehrmals wundern lässt.
Glglgl

5
Abstimmung zur Wiedereröffnung. Irgendwo gibt es einen Grund, auch wenn es einfach "K & R hat nicht daran gedacht" ist.
Telastyn

Antworten:


106

C-Arrays behalten ihre Länge im Auge, da die Array-Länge eine statische Eigenschaft ist:

int xs[42];  /* a 42-element array */

Normalerweise kann diese Länge nicht abgefragt werden, dies ist jedoch nicht erforderlich, da sie ohnehin statisch ist. Deklarieren Sie einfach ein Makro XS_LENGTHfür die Länge, und fertig.

Das wichtigere Problem ist, dass C-Arrays implizit in Zeiger zerfallen, z. B. wenn sie an eine Funktion übergeben werden. Das macht Sinn und erlaubt ein paar nette Tricks auf niedriger Ebene, aber es verliert die Information über die Länge des Arrays. Eine bessere Frage wäre also, warum C mit dieser impliziten Verschlechterung auf Zeiger entworfen wurde.

Eine andere Sache ist, dass Zeiger außer der Speicheradresse selbst keinen Speicher benötigen. Mit C können wir ganze Zahlen in Zeiger und Zeiger auf andere Zeiger umwandeln und Zeiger so behandeln, als wären sie Arrays. Dabei ist C nicht wahnsinnig genug, um eine Array-Länge zu erfinden, sondern scheint auf das Spiderman-Motto zu vertrauen: Mit großer Kraft wird der Programmierer hoffentlich die große Verantwortung erfüllen, Längen und Überläufe im Auge zu behalten.


13
Ich denke, Sie wollen damit sagen, dass C-Compiler statische Array-Längen verfolgen , wenn ich mich nicht irre . Dies ist jedoch nicht gut für Funktionen, die nur einen Zeiger erhalten.
VF1

25
@ VF1 ja. Aber die wichtige Sache ist , dass Arrays und Zeiger sind verschiedene Dinge in C . Angenommen, Sie verwenden keine Compilererweiterungen, können Sie im Allgemeinen kein Array selbst an eine Funktion übergeben, aber Sie können einen Zeiger übergeben und einen Zeiger indizieren, als wäre er ein Array. Sie beschweren sich effektiv darüber, dass Zeiger keine Länge haben. Sie sollten sich darüber beschweren, dass Arrays nicht als Funktionsargumente übergeben werden können oder dass Arrays implizit zu Zeigern degradiert werden.
Amon

37
"Normalerweise können Sie diese Länge nicht abfragen" - tatsächlich ist es der sizeof-Operator - sizeof (xs) würde 168 zurückgeben, vorausgesetzt, Ints sind vier Bytes lang. Um die 42 zu bekommen, mache: sizeof (xs) / sizeof (int)
tcrosley

15
@tcrosley Das funktioniert nur im Rahmen der Array-Deklaration - versuchen Sie, xs als Parameter an eine andere Funktion zu übergeben, und sehen Sie dann, welche Größe von (xs) Sie erhalten ...
Gwyn Evans

26
@ GwynEvans noch einmal: Zeiger sind keine Arrays. Wenn Sie also ein Array als Parameter an eine andere Funktion übergeben, übergeben Sie kein Array, sondern einen Zeiger. Zu behaupten, sizeof(xs)wo xssich ein Array in einem anderen Bereich befinden würde, ist offensichtlich falsch, da der Entwurf von C es Arrays nicht erlaubt, ihren Bereich zu verlassen. Wenn , sizeof(xs)wo xsist ein Array unterscheidet sich von sizeof(xs)denen xsist ein Zeiger, kommt das nicht überraschend , weil Sie Äpfel mit Birnen vergleichen .
Amon

38

Ein Großteil davon hing mit den damals verfügbaren Computern zusammen. Das kompilierte Programm musste nicht nur auf einem Computer mit begrenzten Ressourcen ausgeführt werden, sondern, was vielleicht noch wichtiger ist, der Compiler selbst musste auf diesen Computern ausgeführt werden. Zu der Zeit, als Thompson C entwickelte, verwendete er einen PDP-7 mit 8.000 RAM. Komplexe Sprachfunktionen, bei denen der tatsächliche Maschinencode keine unmittelbare Entsprechung aufwies, wurden einfach nicht in die Sprache aufgenommen.

Wenn Sie die Geschichte von C sorgfältig durchlesen, erhalten Sie ein besseres Verständnis für das oben Genannte, was jedoch nicht ausschließlich auf die Einschränkungen der Maschine zurückzuführen war:

Darüber hinaus kann die Sprache (C) wichtige Konzepte beschreiben, z. B. Vektoren, deren Länge zur Laufzeit mit nur wenigen Grundregeln und Konventionen variiert. ... Es ist interessant, den Ansatz von C mit dem von zwei nahezu zeitgleichen Sprachen zu vergleichen, Algol 68 und Pascal [Jensen 74]. Arrays in Algol 68 haben entweder feste Grenzen oder sind "flexibel": Sowohl in der Sprachdefinition als auch in Compilern ist ein beträchtlicher Mechanismus erforderlich, um flexible Arrays aufzunehmen (und nicht alle Compiler implementieren sie vollständig). Original Pascal hatte nur feste Größen Arrays und Strings, und dies erwies sich als einschränkend [Kernighan 81].

C-Arrays sind von Natur aus leistungsstärker. Das Hinzufügen von Grenzen schränkt ein, wofür der Programmierer sie verwenden kann. Solche Einschränkungen können für Programmierer nützlich sein, sind aber notwendigerweise auch einschränkend.


4
Das stimmt so ziemlich mit der ursprünglichen Frage überein. Dies und die Tatsache, dass C absichtlich "leicht" gehalten wurde, als es darum ging, zu überprüfen, was der Programmierer tat, um es für das Schreiben von Betriebssystemen attraktiv zu machen.
ClickRick

5
Toller Link, sie haben auch explizit das Speichern der Länge von Strings geändert, um ein Trennzeichen zu verwendento avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator
na

5
Die nicht abgeschlossenen Arrays passen auch zum Bare-Metal-Ansatz von C. Beachten Sie, dass das K & R C-Buch weniger als 300 Seiten umfasst und ein Lernprogramm, eine Referenz und eine Liste der Standardaufrufe enthält. Mein O'Reilly Regex-Buch ist fast doppelt so lang wie K & R C.
Michael Shopsin

22

Damals, als C erstellt wurde, waren 4 Byte Platz für jeden String, egal wie kurz er war, eine Verschwendung!

Es gibt noch ein anderes Problem: Denken Sie daran, dass C nicht objektorientiert ist. Wenn Sie also alle Zeichenfolgen mit einem Längenpräfix versehen, muss es als intrinsischer Compilertyp definiert werden, nicht als ein char*. Wenn es sich um einen speziellen Typ handelt, können Sie eine Zeichenfolge nicht mit einer konstanten Zeichenfolge vergleichen.

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

Es müssten spezielle Compiler-Details angegeben werden, um diese statische Zeichenfolge in eine Zeichenfolge zu konvertieren, oder es müssten andere Zeichenfolgenfunktionen verwendet werden, um das Längenpräfix zu berücksichtigen.

Ich denke, letztendlich haben sie einfach nicht das Längenpräfix gewählt, anders als Pascal.


10
Das Überprüfen von Grenzen braucht auch Zeit. Heutzutage trivial, aber etwas, auf das die Leute geachtet haben, als sie sich um 4 Bytes gekümmert haben.
Steven Burnap

18
@StevenBurnap: Es ist auch heute nicht so trivial, wenn Sie sich in einer inneren Schleife befinden, die über jedes Pixel eines 200-MB-Bildes geht. Wenn Sie C schreiben, möchten Sie im Allgemeinen schnell vorgehen und keine Zeit damit verschwenden, bei jeder Iteration, bei der Ihre forSchleife bereits so eingerichtet wurde, dass die Grenzen eingehalten werden, unbrauchbar zu werden.
Matteo Italia

4
@ VF1 "back in the day" könnte es zwei Bytes gewesen sein (DEC PDP / 11 jemand?)
ClickRick

7
Es ist nicht nur "back in the day". Die für die Software, auf die C abzielt, ist eine "portable Assemblersprache" wie Betriebssystem-Kernel, Gerätetreiber, eingebettete Echtzeit-Software usw. usw. Es ist wichtig, ein halbes Dutzend Anweisungen für die Überprüfung von Grenzen zu verschwenden, und in vielen Fällen müssen Sie "außerhalb der Grenzen" sein (wie können Sie einen Debugger schreiben, wenn Sie nicht zufällig auf den Speicher eines anderen Programms zugreifen können?).
James Anderson

3
Dies ist eigentlich ein eher schwaches Argument, wenn man bedenkt, dass BCPL Argumente mit Längenangabe hatte. Genau wie Pascal, obwohl dies auf 1 Wort beschränkt war, also im Allgemeinen nur 8 oder 9 Bits, was ein wenig einschränkend war (es schließt auch die Möglichkeit aus, Teile von Strings gemeinsam zu nutzen, obwohl diese Optimierung für die Zeit wahrscheinlich viel zu weit fortgeschritten war). Und das Deklarieren eines Strings als Struktur mit einer Länge, gefolgt vom Array, würde wirklich keine spezielle Compiler-Unterstützung erfordern.
Voo,

11

In C ist jede zusammenhängende Teilmenge eines Arrays auch ein Array und kann als solches bearbeitet werden. Dies gilt sowohl für Lese- als auch für Schreibvorgänge. Diese Eigenschaft würde nicht gelten, wenn die Größe explizit gespeichert würde.


6
"Das Design wäre anders" spricht nicht gegen ein anderes Design.
VF1

7
@ VF1: Hast du schon mal in Standard Pascal programmiert? Cs Fähigkeit, mit Arrays einigermaßen flexibel zu sein, war eine enorme Verbesserung gegenüber Assembler (überhaupt keine Sicherheit) und der ersten Generation von typsicheren Sprachen (übertriebene Typensicherheit, einschließlich exakter Array-Grenzen)
MSalters

5
Diese Fähigkeit, ein Array aufzuteilen, ist in der Tat ein massives Argument für das C89-Design.

Fortran-Hacker der alten Schule können diese Eigenschaft ebenfalls gut nutzen (allerdings muss die Scheibe an ein Array in Fortran übergeben werden). Verwirrend und schmerzhaft beim Programmieren oder Debuggen, aber schnell und elegant beim Arbeiten.
DMCKEE

3
Es gibt eine interessante Designalternative, die das Schneiden ermöglicht: Speichern Sie die Länge nicht neben den Arrays. Speichern Sie für jeden Zeiger auf ein Array die Länge mit dem Zeiger. (Wenn Sie nur ein echtes C-Array haben, ist die Größe eine Konstante für die Kompilierungszeit, die dem Compiler zur Verfügung steht.) Sie nimmt mehr Platz in Anspruch, ermöglicht jedoch das Schneiden unter Beibehaltung der Länge. Rust macht das &[T]zum Beispiel für die Typen.

8

Das größte Problem beim Kennzeichnen von Arrays mit ihrer Länge ist nicht so sehr der zum Speichern dieser Länge erforderliche Speicherplatz, noch die Frage, wie sie gespeichert werden soll (die Verwendung eines zusätzlichen Bytes für kurze Arrays wäre im Allgemeinen nicht zu beanstanden, und auch nicht die Verwendung von vier zusätzliche Bytes für lange Arrays, aber die Verwendung von vier Bytes auch für kurze Arrays kann sein). Ein viel größeres Problem ist der gegebene Code wie:

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

Die einzige Möglichkeit, mit der der Code den ersten Anruf annehmen ClearTwoElements, den zweiten jedoch ablehnen kann, besteht darin, dass die ClearTwoElementsMethode Informationen empfängt, die ausreichen, um zu wissen, dass sie in jedem Fall einen Verweis auf einen Teil des Arrays empfängt, foound zusätzlich zu wissen, welcher Teil. Das würde in der Regel die Kosten für die Übergabe von Zeigerparametern verdoppeln. Wenn vor jedem Array ein Zeiger auf eine Adresse unmittelbar nach dem Ende steht (das effizienteste Format für die Validierung), würde der optimierte Code für ClearTwoElementswahrscheinlich ungefähr so ​​aussehen:

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

Beachten Sie, dass ein Methodenaufrufer im Allgemeinen durchaus legitimerweise einen Zeiger auf den Anfang des Arrays oder das letzte Element auf eine Methode übergeben kann. Nur wenn die Methode versucht, auf Elemente zuzugreifen, die außerhalb des übergebenen Arrays liegen, verursachen solche Zeiger Probleme. Folglich müsste eine aufgerufene Methode zuerst sicherstellen, dass das Array groß genug ist, dass die Zeigerarithmetik zur Validierung ihrer Argumente selbst keine Grenzen überschreitet, und dann einige Zeigerberechnungen durchführen, um die Argumente zu validieren. Die Zeit, die für eine solche Validierung aufgewendet wird, würde wahrscheinlich die Kosten für echte Arbeit übersteigen. Darüber hinaus könnte die Methode wahrscheinlich effizienter sein, wenn sie geschrieben und aufgerufen wird:

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

Das Konzept eines Typs, der etwas zur Identifizierung eines Objekts mit etwas zur Identifizierung eines Teils davon kombiniert, ist gut. Ein Zeiger im C-Stil ist jedoch schneller, wenn keine Validierung durchgeführt werden muss.


Wenn Arrays eine Laufzeitgröße hätten, würde sich der Zeiger auf das Array grundlegend vom Zeiger auf ein Element des Arrays unterscheiden. Letztere können möglicherweise überhaupt nicht direkt in frühere konvertiert werden (ohne ein neues Array zu erstellen). []Die Syntax für Zeiger ist möglicherweise noch vorhanden, unterscheidet sich jedoch von diesen hypothetischen "echten" Arrays, und das von Ihnen beschriebene Problem ist wahrscheinlich nicht vorhanden.
Hyde

@hyde: Die Frage ist, ob für Zeiger, deren Objektbasisadresse unbekannt ist, Arithmetik zulässig sein soll. Außerdem habe ich eine andere Schwierigkeit vergessen: Arrays innerhalb von Strukturen. Wenn ich darüber nachdenke, bin ich mir nicht sicher, ob es einen Zeigertyp geben würde, der auf ein in einer Struktur gespeichertes Array verweisen könnte, ohne dass jeder Zeiger nicht nur die Adresse des Zeigers selbst, sondern auch die oberen und unteren zulässigen Werte enthalten muss Bereiche, auf die es zugreifen kann.
Supercat

Zwischenpunkt. Ich denke, das reduziert sich immer noch auf Amons Antwort.
VF1

Die Frage fragt nach Arrays. Der Zeiger ist die Speicheradresse und würde sich mit der Prämisse der Frage nicht ändern, soweit die Absicht verstanden wird. Arrays würden Länge bekommen, Zeiger wären unverändert (außer dass Zeiger auf Arrays ein neuer, eindeutiger, eindeutiger Typ sein müssten, ähnlich wie Zeiger auf Struktur).
Hyde

@hyde: Wenn man die Semantik der Sprache ausreichend ändert, ist es möglicherweise möglich, dass Arrays eine zugehörige Länge enthalten, obwohl Arrays, die in Strukturen gespeichert sind, einige Schwierigkeiten bereiten würden. Bei der gegenwärtigen Semantik wäre die Überprüfung der Array-Grenzen nur dann sinnvoll, wenn dieselbe Überprüfung auf Zeiger auf Array-Elemente angewendet würde.
Supercat

7

Einer der fundamentalen Unterschiede zwischen C und den meisten anderen Sprachen der 3. Generation und allen neueren Sprachen, die mir bekannt sind, ist, dass C nicht dazu gedacht ist, das Leben für den Programmierer einfacher oder sicherer zu machen. Es wurde mit der Erwartung entworfen, dass der Programmierer genau wusste, was er tat und genau das tun wollte. Hinter den Kulissen wird nichts unternommen, sodass Sie keine Überraschungen erleben. Sogar die Optimierung auf Compilerebene ist optional (es sei denn, Sie verwenden einen Microsoft-Compiler).

Wenn ein Programmierer Schranken schreiben möchte, die seinen Code überprüfen, ist dies in C einfach genug, aber der Programmierer muss sich dafür entscheiden, den entsprechenden Preis in Bezug auf Speicherplatz, Komplexität und Leistung zu zahlen. Auch wenn ich es jahrelang nicht mehr im Zorn verwendet habe, verwende ich es dennoch im Programmierunterricht, um das Konzept der auf Einschränkungen basierenden Entscheidungsfindung zu vermitteln. Grundsätzlich bedeutet dies, dass Sie sich dafür entscheiden können, alles zu tun, was Sie wollen, aber jede Entscheidung, die Sie treffen, hat einen Preis, dessen Sie sich bewusst sein müssen. Dies wird noch wichtiger, wenn Sie anderen mitteilen, was ihre Programme tun sollen.


3
C war nicht so sehr "designt", als es sich entwickelte. Ursprünglich int f[5];würde eine Deklaration wie nicht fals Array mit fünf Elementen erstellt werden. stattdessen war es äquivalent zu int CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;. Die vorherige Deklaration könnte verarbeitet werden, ohne dass der Compiler die Array-Zeiten wirklich "verstehen" muss. Es musste lediglich eine Assembler-Direktive ausgeben, um Speicherplatz zuzuweisen, und konnte dann vergessen, dass fjemals etwas mit einem Array zu tun hatte. Daraus resultiert das inkonsistente Verhalten von Array-Typen.
Supercat

1
Es stellt sich heraus, dass kein Programmierer weiß, was er in dem Maße tut, wie es C erfordert.
CodesInChaos

7

Kurze Antwort:

Da C a Low-Level - Programmiersprache, erwartet es Sie kümmern sich um diese Fragen selbst zu nehmen, aber dies sorgt für mehr Flexibilität bei der genau wie Sie es umsetzen.

C hat ein Konzept für die Kompilierungszeit eines Arrays, das mit einer Länge initialisiert wird, aber zur Laufzeit wird das Ganze einfach als einzelner Zeiger auf den Beginn der Daten gespeichert. Wenn Sie die Länge des Arrays zusammen mit dem Array an eine Funktion übergeben möchten, tun Sie dies selbst:

retval = my_func(my_array, my_array_length);

Oder Sie könnten eine Struktur mit einem Zeiger und einer Länge oder eine andere Lösung verwenden.

Eine höhere Sprache würde dies als Teil ihres Array-Typs für Sie tun. In C haben Sie die Verantwortung, dies selbst zu tun, aber auch die Flexibilität, zu entscheiden, wie Sie es tun möchten. Und wenn der gesamte Code, den Sie schreiben, bereits die Länge des Arrays kennt, müssen Sie die Länge überhaupt nicht als Variable übergeben.

Der offensichtliche Nachteil besteht darin, dass Sie ohne inhärente Einschränkungen bei der Überprüfung von Arrays, die als Zeiger übergeben werden, gefährlichen Code erstellen können.


1
+1 "Wenn der gesamte Code, den Sie schreiben, bereits die Länge des Arrays kennt, müssen Sie die Länge überhaupt nicht als Variable übergeben."
林果 林果

Wenn nur der Zeiger + Länge struct in die Sprach- und Standardbibliothek eingebrannt worden wäre. So viele Sicherheitslücken hätten vermieden werden können.
CodesInChaos

Dann wäre es nicht wirklich C. Es gibt andere Sprachen, die das tun. C bringt dich auf ein niedriges Niveau.
Thomasrutter

C wurde als Low-Level-Programmiersprache erfunden, und viele Dialekte unterstützen immer noch Low-Level-Programmierung, aber viele Compiler-Autoren bevorzugen Dialekte, die man eigentlich nicht als Low-Level-Sprachen bezeichnen kann. Sie erlauben und erfordern sogar eine Syntax auf niedriger Ebene, versuchen dann jedoch, Konstrukte auf höherer Ebene abzuleiten, deren Verhalten möglicherweise nicht mit der durch die Syntax implizierten Semantik übereinstimmt.
Supercat

5

Das Problem des zusätzlichen Speichers ist ein Problem, aber meiner Meinung nach ein untergeordnetes. Schließlich werden Sie die meiste Zeit ohnehin brauchen, um die Länge zu verfolgen, obwohl amon den Vorteil hatte, dass sie häufig statisch verfolgt werden kann.

Ein größeres Problem ist, wo und wie lange die Länge gespeichert werden muss. Es gibt nicht einen Ort, der in allen Situationen funktioniert. Sie könnten sagen, speichern Sie einfach die Länge im Speicher kurz vor den Daten. Was ist, wenn das Array nicht auf den Speicher zeigt, sondern auf einen UART-Puffer?

Wenn die Länge weggelassen wird, kann der Programmierer seine eigenen Abstraktionen für die jeweilige Situation erstellen, und für den allgemeinen Anwendungsfall stehen zahlreiche vorgefertigte Bibliotheken zur Verfügung. Die eigentliche Frage ist , warum nicht diese Abstraktionen werden verwendet in sicherheitsrelevanten Anwendungen?


1
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?Könnten Sie das bitte etwas näher erläutern? Auch das, was vielleicht zu oft passiert oder es ist nur ein seltener Fall?
Mahdi

Wenn ich es entworfen hätte, wäre ein als geschriebenes Funktionsargument T[]nicht gleichbedeutend mit, T*sondern würde der Funktion ein Tupel mit Zeiger und Größe übergeben. Arrays mit fester Größe könnten zu einem solchen Array-Slice zerfallen, anstatt zu Zeigern wie in C. Der Hauptvorteil dieses Ansatzes ist nicht, dass er für sich allein sicher ist, aber das ist eine Konvention, auf die sich alles, einschließlich der Standardbibliothek, beziehen kann bauen.
CodesInChaos

1

Aus der Entwicklung der C-Sprache :

Es schien, dass Strukturen auf intuitive Weise auf den Speicher in der Maschine abgebildet werden sollten, aber in einer Struktur, die ein Array enthielt, gab es keinen geeigneten Ort, um den Zeiger, der die Basis des Arrays enthielt, zu verstauen, und auch keine geeignete Möglichkeit, dies zu arrangieren initialisiert. Beispielsweise können die Verzeichniseinträge früherer Unix-Systeme in C als beschrieben werden
struct {
    int inumber;
    char    name[14];
};
Ich wollte, dass die Struktur nicht nur ein abstraktes Objekt charakterisiert, sondern auch eine Sammlung von Bits beschreibt, die aus einem Verzeichnis gelesen werden können. Wo konnte der Compiler den Zeiger auf namedas verbergen, was die Semantik verlangte? Selbst wenn Strukturen abstrakter gedacht würden und der Platz für Zeiger irgendwie verborgen werden könnte, wie könnte ich mit dem technischen Problem umgehen, diese Zeiger richtig zu initialisieren, wenn ein kompliziertes Objekt zugewiesen wird, das möglicherweise Strukturen spezifiziert, die Arrays enthalten, die Strukturen beliebiger Tiefe enthalten?

Die Lösung stellte den entscheidenden Sprung in der Evolutionskette zwischen typenlosem BCPL und typisiertem C dar. Sie beseitigte die Materialisierung des Zeigers im Speicher und verursachte stattdessen die Erstellung des Zeigers, wenn der Arrayname in einem Ausdruck erwähnt wird. Die Regel, die im heutigen C überlebt, lautet, dass Werte des Array-Typs in Ausdrücken in Zeiger auf das erste der Objekte konvertiert werden, aus denen das Array besteht.

In dieser Passage wird erläutert, warum Array-Ausdrücke in den meisten Fällen in Zeiger zerfallen. Die gleiche Argumentation gilt jedoch auch für den Fall, dass die Array-Länge nicht im Array selbst gespeichert wird. Wenn Sie eine Eins-zu-Eins-Zuordnung zwischen der Typdefinition und ihrer Darstellung im Speicher wünschen (wie es Ritchie getan hat), gibt es keinen geeigneten Ort zum Speichern dieser Metadaten.

Denken Sie auch an mehrdimensionale Arrays. Wo würden Sie die Längenmetadaten für jede Dimension speichern, sodass Sie immer noch mit so etwas wie durch das Array gehen können?

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );

-2

Die Frage geht davon aus, dass es in C Arrays gibt. Dinge, die als Arrays bezeichnet werden, sind nur ein syntaktischer Zucker für Operationen mit fortlaufenden Folgen von Daten und Zeigerarithmetik.

Der folgende Code kopiert einige Daten von src nach dst in int-size-Blöcken, ohne zu wissen, dass es sich tatsächlich um eine Zeichenfolge handelt.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

Warum ist C so vereinfacht, dass es keine richtigen Arrays hat? Ich weiß keine richtige Antwort auf diese neue Frage. Aber manche Leute sagen oft, dass C nur (etwas) lesbarer und portabler Assembler ist.


2
Ich glaube nicht, dass Sie die Frage beantwortet haben.
Robert Harvey

2
Was Sie gesagt haben, ist wahr, aber der Fragende möchte wissen, warum dies der Fall ist.

9
Denken Sie daran, einer der Spitznamen für C ist "Portable Assembly". Während neuere Versionen des Standards Konzepte auf höherer Ebene hinzugefügt haben, besteht es im Kern aus einfachen Konstrukten und Anweisungen auf niedriger Ebene, die für die meisten nicht-trivialen Maschinen gleich sind. Dies bestimmt die meisten Entwurfsentscheidungen, die in der Sprache getroffen werden. Die einzigen Variablen, die zur Laufzeit vorhanden sind, sind Ganzzahlen, Gleitkommazahlen und Zeiger. Die Anweisungen umfassen Arithmetik, Vergleiche und Sprünge. Fast alles andere ist eine dünne Schicht, die darauf aufgebaut ist.

8
Es ist falsch zu sagen, dass C keine Arrays hat, wenn man bedenkt, dass man nicht dieselbe Binärdatei mit anderen Konstrukten generieren kann (zumindest nicht, wenn man die Verwendung von #defines zur Bestimmung der Arraygrößen in Betracht zieht). Arrays in C sind "kontinuierliche Sequenzen von Daten", nichts Süßes. Die Verwendung von Zeigern wie Arrays ist hier der syntaktische Zucker (anstelle expliziter Zeigerarithmetik), nicht Arrays.
Hyde

2
Ja, sollten Sie diesen Code: struct Foo { int arr[10]; }. arrist ein Array, kein Zeiger.
Steven Burnap
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.