Ich versuche nicht, die übliche Frage zu wiederholen, dass C keine Arrays zurückgeben kann, sondern sich etwas tiefer damit befasst.
Wir können das nicht machen:
char f(void)[8] {
char ret;
// ...fill...
return ret;
}
int main(int argc, char ** argv) {
char obj_a[10];
obj_a = f();
}
Aber wir können tun:
struct s { char arr[10]; };
struct s f(void) {
struct s ret;
// ...fill...
return ret;
}
int main(int argc, char ** argv) {
struct s obj_a;
obj_a = f();
}
Also habe ich den von gcc -S generierten ASM-Code überflogen und scheint mit dem Stack zu arbeiten, der -x(%rbp)
wie bei jeder anderen C-Funktionsrückgabe adressiert .
Was ist mit der direkten Rückgabe von Arrays? Ich meine, nicht in Bezug auf Optimierung oder Rechenkomplexität, sondern in Bezug auf die tatsächliche Fähigkeit, dies ohne die Strukturschicht zu tun.
Zusätzliche Daten: Ich verwende Linux und gcc auf einem x64 Intel.
&
einem ihrer Mitglieder zu übernehmen und auf dieses zuzugreifen. Dies bedeutet, dass Strukturen möglicherweise nicht als Einheit zugewiesen oder kopiert werden und dass sie nicht an Funktionen übergeben oder von diesen zurückgegeben werden können. (Diese Einschränkungen werden in den kommenden Versionen aufgehoben.) … Und zum Glück wurden sie entfernt. Die Einschränkung der Array-Zuweisung hat sich jedoch noch nicht geändert.char f[8](void)
ein Array von Funktionen ist. Eine Funktion, die ein Array zurückgibt, sieht aus wiechar f(void)[8]
.Antworten:
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
und irgendwo in der Definition der Funktion, die
f()
wir habenchar ret[10]; /* ... fill ... */ 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
ret
es 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:
Die drei Ausnahmen sind, wenn das Array der Operand eines
sizeof
Operators 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 einesarrayval
Operators 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
a
undb
nicht gleich groß.)Angesichts des Funktionsprototyps
char f(void)[8];
wir könnten es auch tun
Schauen wir uns die
f
hypothetische Definition an. Wir könnten so etwas habenchar f(void)[8] { char ret[8]; /* ... fill ... */ 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 etwasarrayval()
existierte, tatsächlich funktionieren würde! Insbesondere würde es nicht das Problem haben, einen bald ungültigen Zeiger auf das lokale Array zurückzugebenret
. 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 legalenint g(void) { int ret; /* ... compute ... */ 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.)quelle
f().arr
handelt es sich um ein rvalue-Array.Es hat nichts mit der Fähigkeit an sich zu tun . Andere Sprachen bieten die Möglichkeit, Arrays zurückzugeben, und Sie wissen bereits, dass Sie in C eine Struktur mit einem Array-Mitglied zurückgeben können. Auf der anderen Seite haben noch andere Sprachen die gleiche Einschränkung wie C und noch mehr. Java kann beispielsweise weder Arrays noch Objekte jeglicher Art von Methoden zurückgeben. Es können nur Grundelemente und Verweise auf Objekte zurückgegeben werden.
Nein, es ist einfach eine Frage des Sprachdesigns. Wie bei den meisten anderen Dingen, die mit Arrays zu tun haben, drehen sich die Entwurfspunkte hier um die Bestimmung von C, dass Ausdrücke vom Array-Typ in fast allen Kontexten automatisch in Zeiger konvertiert werden. Der in einer
return
Anweisung angegebene Wert ist keine Ausnahme, sodass C nicht einmal die Rückgabe eines Arrays selbst ausdrücken kann. Eine andere Wahl hätte getroffen werden können, war es aber einfach nicht.quelle
return (array) b
oderreturn b
explizit unterscheiden müssen. Und natürlich kannstruct
s diese Mehrdeutigkeit nicht haben.Damit Arrays erstklassige Objekte sind, müssen Sie sie zumindest zuweisen können. Dies erfordert jedoch Kenntnisse über die Größe, und das C-Typ-System ist nicht leistungsfähig genug, um Größen an Typen anzuhängen. C ++ könnte dies tun, jedoch nicht aufgrund älterer Bedenken - es enthält Verweise auf Arrays bestimmter Größe (
typedef char (&some_chars)[32]
), aber einfache Arrays werden immer noch implizit in Zeiger konvertiert, wie in C. C ++ hat stattdessen std :: array, was im Grunde das ist oben genanntes Array innerhalb der Struktur plus etwas syntaktischen Zucker.quelle
sizeof
Bediener dann arbeiten? Aber es funktioniert auch mit Array-Typen.sizeof
funktioniert gut, @RomanOdaisky. Es ist die Bedeutung der Parameterliste dieser Funktion, die Sie anscheinend überraschend finden. Gemäß dem Standard wird der Parameter ungeachtet der Größe in Klammern als Array und nicht als Arrayx
deklariertchar *
. Dies steht völlig im Einklang mit der Tatsache, dass es überhaupt keine Möglichkeit gibt, ein Array als Funktionsargument zu übergeben (da in der Argumentliste für einen Funktionsaufruf wie an den meisten anderen Stellen Ausdrücke mit Array-Typ in Zeiger konvertiert werden). .int (*p)[3]; printf("%uz\n", sizeof(*p));
.Ich fürchte, es ist nicht so sehr eine Debatte über Objekte der ersten oder zweiten Klasse, sondern eine religiöse Diskussion über bewährte Praktiken und anwendbare Praktiken für tief eingebettete Anwendungen.
Das Zurückgeben einer Struktur bedeutet entweder, dass eine Stammstruktur durch Verstohlenheit in den Tiefen der Aufrufsequenz geändert wird, oder dass Daten dupliziert werden und große Teile duplizierter Daten übergeben werden. Die Hauptanwendungen von C konzentrieren sich immer noch weitgehend auf die tief eingebetteten Anwendungen. In diesen Domänen haben Sie kleine Prozessoren, die keine großen Datenblöcke übergeben müssen. Sie verfügen auch über technische Erfahrung, die es erforderlich macht, ohne dynamische RAM-Zuweisung und mit minimalem Stapel und häufig ohne Heap arbeiten zu können. Es könnte argumentiert werden, dass die Rückkehr der Struktur die gleiche ist wie die Modifikation über einen Zeiger, aber in der Syntax abstrahiert ... Ich fürchte, ich würde argumentieren, dass dies nicht in der C-Philosophie von "Was Sie sehen, ist was Sie bekommen" in der Genauso wie ein Zeiger auf einen Typ ist.
Persönlich würde ich behaupten, dass Sie eine Lücke gefunden haben, ob standardmäßig genehmigt oder nicht. C ist so konzipiert, dass die Zuordnung explizit ist. Sie übergeben als bewährte Methode Objekte in Busgröße, normalerweise in einem angestrebten Zyklus, und beziehen sich dabei auf Speicher, der explizit zu einem kontrollierten Zeitpunkt innerhalb des Entwickler-Ken zugewiesen wurde. Dies ist in Bezug auf Codeeffizienz und Zykluseffizienz sinnvoll und bietet die größtmögliche Kontrolle und Klarheit des Zwecks. Ich fürchte, bei der Code-Inspektion würde ich eine Funktion verwerfen, die eine Struktur als schlechte Praxis zurückgibt. C setzt nicht viele Regeln durch, es ist in vielerlei Hinsicht eine Sprache für professionelle Ingenieure, da der Benutzer seine eigene Disziplin durchsetzen muss. Nur weil du kannst, heißt das nicht, dass du ...
quelle