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:
- Wenn Länge in einem Puffer verfügbar ist, kann ein Pufferüberlauf verhindert werden.
- Ein Java-Stil
arr.length
ist klar und vermeidet, dass der Programmierer vieleint
s auf dem Stapel halten muss, wenn er mit mehreren Arrays arbeitet - 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.
malloc()
ed-Bereichs nicht auf tragbare Weise abgefragt werden?" Das ist eine Sache, die mich mehrmals wundern lässt.Antworten:
C-Arrays behalten ihre Länge im Auge, da die Array-Länge eine statische Eigenschaft ist:
Normalerweise kann diese Länge nicht abgefragt werden, dies ist jedoch nicht erforderlich, da sie ohnehin statisch ist. Deklarieren Sie einfach ein Makro
XS_LENGTH
fü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.
quelle
sizeof(xs)
woxs
sich 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)
woxs
ist ein Array unterscheidet sich vonsizeof(xs)
denenxs
ist ein Zeiger, kommt das nicht überraschend , weil Sie Äpfel mit Birnen vergleichen .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:
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.
quelle
to 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
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.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.
quelle
for
Schleife bereits so eingerichtet wurde, dass die Grenzen eingehalten werden, unbrauchbar zu werden.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.
quelle
&[T]
zum Beispiel für die Typen.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:
Die einzige Möglichkeit, mit der der Code den ersten Anruf annehmen
ClearTwoElements
, den zweiten jedoch ablehnen kann, besteht darin, dass dieClearTwoElements
Methode Informationen empfängt, die ausreichen, um zu wissen, dass sie in jedem Fall einen Verweis auf einen Teil des Arrays empfängt,foo
und 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ürClearTwoElements
wahrscheinlich ungefähr so aussehen: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:
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.
quelle
[]
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.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.
quelle
int f[5];
würde eine Deklaration wie nichtf
als Array mit fünf Elementen erstellt werden. stattdessen war es äquivalent zuint 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, dassf
jemals etwas mit einem Array zu tun hatte. Daraus resultiert das inkonsistente Verhalten von Array-Typen.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:
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.
quelle
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?
quelle
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?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.Aus der Entwicklung der C-Sprache :
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?
quelle
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.
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.
quelle
struct Foo { int arr[10]; }
.arr
ist ein Array, kein Zeiger.