Zunächst einmal können Sie ein Array in eine Struktur einkapseln und dann mit dieser Struktur alles tun, was Sie wollen (zuweisen, von einer Funktion zurückgeben usw.).
Zweitens hat der Compiler, wie Sie festgestellt haben, kaum Schwierigkeiten, Code auszugeben, um Strukturen zurückzugeben (oder zuzuweisen). Das ist also auch nicht der Grund, warum Sie keine Arrays zurückgeben können.
Der grundlegende Grund, warum Sie dies nicht tun können, ist, dass Arrays in C Datenstrukturen zweiter Klasse sind . Alle anderen Datenstrukturen sind erstklassig. Was sind die Definitionen von "erstklassig" und "zweitklassig" in diesem Sinne? Einfach, dass Typen zweiter Klasse nicht zugewiesen werden können.
(Ihre nächste Frage lautet wahrscheinlich: "Gibt es außer Arrays noch andere Datentypen zweiter Klasse?", Und ich denke, die Antwort lautet "Nicht wirklich, es sei denn, Sie zählen Funktionen".)
Eng verbunden mit der Tatsache, dass Sie keine Arrays zurückgeben (oder zuweisen) können, gibt es auch keine Werte vom Array-Typ. Es gibt Objekte (Variablen) vom Array-Typ, aber wenn Sie versuchen, den Wert eins anzunehmen, erhalten Sie einen Zeiger auf das erste Element des Arrays. [Fußnote: Formal gibt es keine Werte vom Array-Typ, obwohl ein Objekt vom Array-Typ als l-Wert betrachtet werden kann , wenn auch nicht zuweisbar.]
Also, ganz abgesehen von der Tatsache , dass Sie nicht zuordnen können zu einem Array, können Sie auch keinen Wert erzeugen , um einen Array zuweisen. Wenn du sagst
char a[10], b[10];
a = b;
Es ist, als hättest du geschrieben
a = &b[0];
Wir haben also rechts einen Zeiger und links ein Array, und wir hätten eine massive Typinkongruenz, selbst wenn Arrays irgendwie zuweisbar wären. Ähnlich (aus Ihrem Beispiel), wenn wir versuchen zu schreiben
a = f();
und irgendwo in der Definition der Funktion, die f()wir haben
char ret[10];
return ret;
Es ist, als ob diese letzte Zeile sagte
return &ret[0];
und wieder haben wir keinen Array-Wert, den wir zurückgeben und zuweisen können a, sondern nur einen Zeiger.
(Im Beispiel für einen Funktionsaufruf haben wir auch das sehr wichtige Problem, dass retes sich um ein lokales Array handelt, das gefährlich ist, wenn Sie versuchen, in C zurückzukehren. Mehr dazu später.)
Ein Teil Ihrer Frage lautet wahrscheinlich "Warum ist das so?" Und auch "Wenn Sie keine Arrays zuweisen können, warum können Sie Strukturen zuweisen, die Arrays enthalten?".
Was folgt, ist meine Interpretation und meine Meinung, aber es stimmt mit dem überein, was Dennis Ritchie in der Arbeit Die Entwicklung der C-Sprache beschreibt .
Die Nichtzuweisbarkeit von Arrays ergibt sich aus drei Tatsachen:
C soll syntaktisch und semantisch nahe an der Maschinenhardware liegen. Eine elementare Operation in C sollte bis zu einer oder wenigen Maschinenanweisungen kompiliert werden, die einen oder mehrere Prozessorzyklen benötigen.
Arrays waren schon immer etwas Besonderes, insbesondere in Bezug auf Zeiger. Diese besondere Beziehung entwickelte sich aus der Behandlung von Arrays in der Vorgängersprache B von C und wurde stark von dieser beeinflusst.
Strukturen waren anfangs nicht in C.
Aufgrund von Punkt 2 ist es unmöglich, Arrays zuzuweisen, und aufgrund von Punkt 1 sollte dies sowieso nicht möglich sein, da ein einzelner Zuweisungsoperator =nicht zu Code erweitert werden sollte, der möglicherweise N Tausend Zyklen zum Kopieren eines N Tausend-Elemente-Arrays benötigt.
Und dann kommen wir zu Punkt 3, der wirklich einen Widerspruch bildet.
Wenn C Strukturen bekam, waren sie anfangs auch nicht ganz erstklassig, da man sie nicht zuweisen oder zurückgeben konnte. Der Grund, warum Sie dies nicht konnten, war einfach, dass der erste Compiler zunächst nicht klug genug war, um den Code zu generieren. Es gab keine syntaktische oder semantische Straßensperre wie bei Arrays.
Das Ziel war von Anfang an, dass die Strukturen erstklassig sind, und dies wurde relativ früh erreicht, kurz zu der Zeit, als die erste Ausgabe von K & R gedruckt werden sollte.
Die große Frage bleibt jedoch: Wenn eine Elementaroperation auf eine kleine Anzahl von Anweisungen und Zyklen kompiliert werden soll, warum lässt dieses Argument die Strukturzuweisung nicht zu? Und die Antwort lautet: Ja, das ist ein Widerspruch.
Ich glaube (obwohl dies mehr Spekulationen meinerseits sind), dass das Denken ungefähr so war: "Erstklassige Typen sind gut, zweitklassige Typen sind unglücklich. Wir haben den Status zweiter Klasse für Arrays, aber wir können Machen Sie es besser mit Strukturen. Die No-Teure-Code-Regel ist nicht wirklich eine Regel, sondern eher eine Richtlinie. Arrays sind oft groß, aber Strukturen sind normalerweise klein, zehn oder Hunderte von Bytes, sodass sie nicht zugewiesen werden normalerweise zu teuer sein. "
Eine konsequente Anwendung der No-Teure-Code-Regel blieb also auf der Strecke. C war sowieso nie perfekt regelmäßig oder konsistent. (Auch die überwiegende Mehrheit der erfolgreichen Sprachen ist weder menschlich noch künstlich.)
Nach alledem kann es sich lohnen zu fragen: "Was wäre, wenn C das Zuweisen und Zurückgeben von Arrays unterstützen würde? Wie könnte das funktionieren?" Und die Antwort muss eine Möglichkeit beinhalten, das Standardverhalten von Arrays in Ausdrücken auszuschalten, nämlich dass sie dazu neigen, sich in Zeiger auf ihr erstes Element zu verwandeln.
Irgendwann in den 90er Jahren, IIRC, gab es einen ziemlich gut durchdachten Vorschlag, genau dies zu tun. Ich denke, es ging darum, einen Array-Ausdruck in [ ]oder [[ ]]oder so einzuschließen. Heute kann ich anscheinend keine Erwähnung dieses Vorschlags finden (obwohl ich dankbar wäre, wenn jemand eine Referenz liefern könnte). Ich glaube jedenfalls, wir könnten C erweitern, um die Array-Zuweisung zu ermöglichen, indem wir die folgenden drei Schritte ausführen:
Entfernen Sie das Verbot der Verwendung eines Arrays auf der linken Seite eines Zuweisungsoperators.
Entfernen Sie das Verbot, Funktionen mit Array-Werten zu deklarieren. Zurück zur ursprünglichen Frage, machen Sie char f(void)[8] { ... }legal.
(Dies ist das große Problem.) Sie können ein Array in einem Ausdruck erwähnen und erhalten einen wahren, zuweisbaren Wert (einen r- Wert ) vom Array-Typ. Aus Gründen der Argumentation werde ich einen neuen Operator oder eine neue Pseudofunktion mit dem Namen setzen arrayval( ... ).
[Randnotiz: Heute haben wir eine " Schlüsseldefinition " der Array / Zeiger-Korrespondenz, nämlich:
Ein Verweis auf ein Objekt vom Typ Array, das in einem Ausdruck erscheint, zerfällt (mit drei Ausnahmen) in einen Zeiger auf sein erstes Element.
Die drei Ausnahmen sind, wenn das Array der Operand eines sizeofOperators oder eines &Operators oder ein String-Literal-Initialisierer für ein Zeichenarray ist. Unter den hypothetischen Änderungen, die ich hier diskutiere, gibt es vier Ausnahmen, wobei der Operand eines arrayvalOperators zur Liste hinzugefügt wird.]
Wie auch immer, mit diesen Änderungen könnten wir Dinge wie schreiben
char a[8], b[8] = "Hello";
a = arrayval(b);
(Natürlich müssten wir auch entscheiden, was zu tun ist, wenn aund bnicht gleich groß.)
Angesichts des Funktionsprototyps
char f(void)[8];
wir könnten es auch tun
a = f();
Schauen wir uns die fhypothetische Definition an. Wir könnten so etwas haben
char f(void)[8] {
char ret[8];
return arrayval(ret);
}
Beachten Sie, dass dies (mit Ausnahme des hypothetischen neuen arrayval()Operators) genau das ist, was Dario Rodriguez ursprünglich gepostet hat. Beachten Sie auch, dass dies in der hypothetischen Welt, in der die Zuweisung von Arrays legal war und so etwas arrayval()existierte, tatsächlich funktionieren würde! Insbesondere würde es nicht das Problem haben, einen bald ungültigen Zeiger auf das lokale Array zurückzugeben ret. Es würde eine Kopie des Arrays zurückgeben, so dass es überhaupt kein Problem geben würde - es wäre fast vollkommen analog zu dem offensichtlich legalen
int g(void) {
int ret;
return ret;
}
Zurück zur Nebenfrage "Gibt es noch andere Typen zweiter Klasse?": Ich denke, es ist mehr als ein Zufall, dass Funktionen wie Arrays automatisch ihre Adresse erhalten, wenn sie nicht als sie selbst verwendet werden (d. H. als Funktionen oder Arrays), und dass es ebenfalls keine Werte vom Funktionstyp gibt. Aber dies ist meistens eine müßige Überlegung, weil ich nicht glaube, dass ich jemals Funktionen gehört habe, die in C als "Typen zweiter Klasse" bezeichnet werden. (Vielleicht haben sie das und ich habe es vergessen.)
Fußnote: Da der Compiler ist bereit zu assign Strukturen, und in der Regel weiß , wie effizienten Code zu emittieren , für so tun, es verwendete ein etwas beliebter Trick zu kooptieren zu den Compiler des Struktur-Kopier - Maschinen , um beliebiges Bytes von Punkt a zu kopieren zu Punkt b. Insbesondere könnten Sie dieses etwas seltsam aussehende Makro schreiben:
#define MEMCPY(b, a, n) (*(struct foo { char x[n]; } *)(b) = \
*(struct foo *)(a))
das verhielt sich mehr oder weniger genau wie eine optimierte Inline-Version von memcpy(). (Tatsächlich kompiliert und funktioniert dieser Trick auch heute noch unter modernen Compilern.)