Warum unterstützen C und C ++ die Mitgliedszuweisung von Arrays innerhalb von Strukturen, jedoch nicht generell?

85

Ich verstehe, dass die Mitgliedszuweisung von Arrays nicht unterstützt wird, so dass Folgendes nicht funktioniert:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

Ich habe dies nur als Tatsache akzeptiert und festgestellt, dass das Ziel der Sprache darin besteht, ein offenes Framework bereitzustellen und den Benutzer entscheiden zu lassen, wie etwas wie das Kopieren eines Arrays implementiert werden soll.

Folgendes funktioniert jedoch:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

Das Array num[3]wird von seiner Instanz in struct1in seine Instanz in mitgliedsbezogen zugewiesen struct2.

Warum wird die Mitgliederzuweisung von Arrays für Strukturen unterstützt, jedoch nicht generell?

edit : Roger Pates Kommentar im Thread std :: string in struct - Probleme beim Kopieren / Zuweisen? scheint in die allgemeine Richtung der Antwort zu zeigen, aber ich weiß nicht genug, um es selbst zu bestätigen.

edit 2 : Viele ausgezeichnete Antworten. Ich habe mich für Luther Blissett entschieden , weil ich mich hauptsächlich über die philosophischen oder historischen Gründe für das Verhalten gewundert habe , aber auch James McNellis 'Verweis auf die zugehörige Spezifikationsdokumentation war nützlich.

Ozmo
quelle
5
Ich mache, dass dies sowohl C als auch C ++ als Tags hat, da dies von C stammt. Auch eine gute Frage.
GManNickG
4
Es könnte erwähnenswert sein, dass vor langer Zeit in C eine Strukturzuweisung im Allgemeinen nicht möglich war und Sie sie verwenden mussten memcpy()oder ähnliches.
ggg
Nur ein kleines FYI ... boost::array( boost.org/doc/libs/release/doc/html/array.html ) und jetzt std::array( en.cppreference.com/w/cpp/container/array ) sind STL-kompatible Alternativen zu chaotisch alte C-Arrays. Sie unterstützen die Zuweisung von Kopien.
Emile Cormier
@ EmileCormier Und sie sind - tada! - Strukturen um Arrays.
Peter - Stellen Sie Monica

Antworten:

45

Hier ist meine Meinung dazu:

Die Entwicklung der C-Sprache bietet einige Einblicke in die Entwicklung des Array-Typs in C:

Ich werde versuchen, das Array zu skizzieren:

Die Vorläufer B und BCPL von C hatten keinen bestimmten Array-Typ, eine Deklaration wie:

auto V[10] (B)
or 
let V = vec 10 (BCPL)

würde V als (nicht typisierten) Zeiger deklarieren, der so initialisiert wird, dass er auf einen nicht verwendeten Bereich von 10 "Wörtern" Speicher zeigt. B bereits verwendet *für Zeiger Dereferenzierung und hatte die [] verkürzte Schreibweise, *(V+i)gemeint V[i], ebenso wie in C / C ++ heute. Ist Vjedoch kein Array, ist es immer noch ein Zeiger, der auf einen Speicher zeigen muss. Dies verursachte Probleme, als Dennis Ritchie versuchte, B mit Strukturtypen zu erweitern. Er wollte, dass Arrays Teil der Strukturen sind, wie in C heute:

struct {
    int inumber;
    char name[14];
};

Mit dem B, BCPL-Konzept von Arrays als Zeiger hätte das nameFeld jedoch einen Zeiger enthalten müssen, der zur Laufzeit auf einen Speicherbereich von 14 Bytes innerhalb der Struktur initialisiert werden musste . Das Initialisierungs- / Layoutproblem wurde schließlich gelöst, indem Arrays eine Sonderbehandlung gegeben wurden: Der Compiler verfolgte die Position von Arrays in Strukturen, auf dem Stapel usw., ohne dass der Zeiger auf die Daten tatsächlich materialisiert werden musste, außer in Ausdrücken, die die Arrays betreffen. Diese Behandlung ermöglichte es, dass fast der gesamte B-Code noch ausgeführt wird, und ist die Quelle der Regel "Arrays werden in Zeiger konvertiert, wenn Sie sie betrachten" . Es ist ein Kompatibilitäts-Hack, der sich als sehr praktisch herausstellte, da er Arrays mit offener Größe usw. erlaubte.

Und hier ist meine Vermutung, warum ein Array nicht zugewiesen werden kann: Da Arrays Zeiger in B waren, können Sie einfach schreiben:

auto V[10];
V=V+5;

ein "Array" neu zu gründen. Dies war jetzt bedeutungslos, da die Basis einer Array-Variablen kein Wert mehr war. Daher wurde diese Zuweisung nicht zugelassen, was dazu beitrug, die wenigen Programme zu erfassen, die diese Neuausrichtung auf deklarierten Arrays durchgeführt haben. Und dann blieb dieser Gedanke bestehen: Da Arrays niemals so konzipiert wurden, dass sie vom C-Typ-System erstklassig zitiert werden, wurden sie meist als spezielle Bestien behandelt, die zu Zeigern werden, wenn Sie sie verwenden. Und unter einem bestimmten Gesichtspunkt (der ignoriert, dass C-Arrays ein verpatzter Hack sind) ist es immer noch sinnvoll, die Array-Zuweisung nicht zuzulassen: Ein offenes Array oder ein Array-Funktionsparameter wird als Zeiger ohne Größeninformationen behandelt. Der Compiler verfügt nicht über die Informationen, um eine Array-Zuweisung für sie zu generieren, und die Zeigerzuweisung war aus Kompatibilitätsgründen erforderlich.

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

Dies änderte sich nicht, als eine Überarbeitung von C 1978 die Strukturzuweisung hinzufügte ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). Obwohl Aufzeichnungen waren verschiedene Typen in C, es war nicht möglich , sie in frühen K & R C. Sie zuweisen hatte sie Mitglied weise mit memcpy zu kopieren und Sie nur Zeiger auf sie als Funktionsparameter übergeben konnte. Die Zuweisung (und Parameterübergabe) wurde nun einfach als Speicher des Rohspeichers der Struktur definiert, und da dies den vorhandenen Code nicht beschädigen konnte, wurde er leicht hinzugefügt. Als unbeabsichtigter Nebeneffekt führte dies implizit zu einer Art Array-Zuweisung, die jedoch irgendwo innerhalb einer Struktur auftrat, sodass dies nicht wirklich zu Problemen bei der Verwendung von Arrays führen konnte.

Nordischer Mainframe
quelle
Es ist schade, dass C keine Syntax definiert hat, z. B. int[10] c;um den l-Wert cals Array von zehn Elementen zu verhalten, anstatt als Zeiger auf das erste Element eines Arrays mit zehn Elementen. Es gibt einige Situationen, in denen es nützlich ist, ein typedef zu erstellen, das Speicherplatz zuweist, wenn es für eine Variable verwendet wird, aber einen Zeiger übergibt, wenn es als Funktionsargument verwendet wird. Die Unfähigkeit, einen Wert vom Typ Array zu haben, ist jedoch eine erhebliche semantische Schwäche in der Sprache.
Supercat
Anstatt "Zeiger, der auf einen Speicher zeigen muss" zu sagen, ist der wichtige Punkt, dass der Zeiger selbst wie ein regulärer Zeiger im Speicher gespeichert werden muss . Dies kommt in Ihrer späteren Erklärung zum Ausdruck, aber ich denke, das hebt den Hauptunterschied besser hervor. (Im modernen C bezieht sich der Name einer Array-Variablen auf einen Speicherblock, das ist also nicht der Unterschied. Es ist so, dass der Zeiger selbst nirgendwo in der abstrakten Maschine logisch gespeichert ist.)
Peter Cordes
Siehe C Abneigung gegen Arrays für eine schöne Zusammenfassung der Geschichte.
Peter Cordes
29

In Bezug auf die Zuweisungsoperatoren sagt der C ++ - Standard Folgendes aus (C ++ 03 §5.17 / 1):

Es gibt mehrere Zuweisungsoperatoren ... alle benötigen einen modifizierbaren l-Wert als linken Operanden

Ein Array ist kein veränderbarer Wert.

Die Zuordnung zu einem Klassentypobjekt ist jedoch speziell definiert (§5.17 / 4):

Die Zuordnung zu Objekten einer Klasse wird vom Kopierzuweisungsoperator definiert.

Wir wollen also sehen, was der implizit deklarierte Kopierzuweisungsoperator für eine Klasse tut (§12.8 / 13):

Der implizit definierte Kopierzuweisungsoperator für die Klasse X führt die Elementzuweisung seiner Unterobjekte durch. ... Jedes Unterobjekt wird in der seinem Typ entsprechenden Weise zugewiesen:
...
- Wenn das Unterobjekt ein Array ist, wird jedes Element in der dem Elementtyp entsprechenden Weise zugewiesen
...

Für ein Klassentypobjekt werden Arrays also korrekt kopiert. Beachten Sie, dass Sie dies nicht nutzen können, wenn Sie einen vom Benutzer deklarierten Kopierzuweisungsoperator angeben, und dass Sie das Array Element für Element kopieren müssen.


Die Argumentation ist in C ähnlich (C99 §6.5.16 / 2):

Ein Zuweisungsoperator muss einen modifizierbaren Wert als linken Operanden haben.

Und §6.3.2.1 / 1:

Ein modifizierbarer Wert ist ein Wert ohne Array-Typ ... [andere Einschränkungen folgen]

In C ist die Zuweisung viel einfacher als in C ++ (§6.5.16.1 / 2):

Bei der einfachen Zuweisung (=) wird der Wert des rechten Operanden in den Typ des Zuweisungsausdrucks konvertiert und ersetzt den Wert, der in dem durch den linken Operanden angegebenen Objekt gespeichert ist.

Für die Zuweisung von Objekten vom Typ Struktur müssen der linke und der rechte Operand denselben Typ haben, sodass der Wert des rechten Operanden einfach in den linken Operanden kopiert wird.

James McNellis
quelle
1
Warum sind Arrays unveränderlich? Oder warum wird die Zuweisung nicht speziell für Arrays definiert, wie es ist, wenn es sich um einen Klassentyp handelt?
GManNickG
1
@ GM: Das ist die interessantere Frage, nicht wahr? Für C ++ lautet die Antwort wahrscheinlich "weil es so in C ist", und für C würde ich vermuten, dass es nur daran liegt, wie sich die Sprache entwickelt hat (dh der Grund ist historisch, nicht technisch), aber ich war nicht am Leben Wenn das meiste davon stattgefunden hat, überlasse ich es jemandem, der besser informiert ist, diesen Teil zu beantworten :-P (FWIW, ich kann nichts in den Begründungsdokumenten von C90 oder C99 finden).
James McNellis
1
Weiß jemand, wo die Definition von "modifizierbarem Wert" im C ++ 03-Standard ist? Es sollte in §3.10 sein. Der Index sagt, dass er auf dieser Seite definiert ist, aber nicht. In der (nicht normativen) Anmerkung in §8.3.4 / 5 heißt es: "Objekte von Array-Typen können nicht geändert werden, siehe 3.10", aber in §3.10 wird das Wort "Array" nicht einmal verwendet.
James McNellis
@ James: Ich habe genau das Gleiche getan. Es scheint sich auf eine entfernte Definition zu beziehen. Und ja, ich wollte schon immer den wahren Grund dafür wissen, aber es scheint ein Rätsel zu sein. Ich habe Dinge wie "Verhindern, dass Leute ineffizient sind, indem sie versehentlich Arrays zuweisen" gehört, aber das ist lächerlich.
GManNickG
@GMan, James: Vor kurzem gab es eine Diskussion über comp.lang.c ++ groups.google.com/group/comp.lang.c++/browse_frm/thread/…, wenn Sie es verpasst haben und immer noch interessiert sind. Offenbar ist es nicht , weil ein Array keine modifizierbar lvalue ist (ein Array ist sicherlich ein L - Wert und all nicht-const lvalues veränderbar ist), sondern weil =ein erfordert rvalue auf der RHS und ein Array kann nicht sein rvalue ! Die Konvertierung von lWert zu rWert ist für Arrays verboten, die durch lWert zu Zeiger ersetzt werden. static_castist nicht besser darin, einen Wert zu erstellen, weil er in denselben Begriffen definiert ist.
Potatoswatter
2

In diesem Link: http://www2.research.att.com/~bs/bs_faq2.html gibt es einen Abschnitt zur Array-Zuweisung:

Die zwei grundlegenden Probleme mit Arrays sind die folgenden

  • Ein Array kennt seine eigene Größe nicht
  • Der Name eines Arrays wird bei der geringsten Provokation in einen Zeiger auf sein erstes Element konvertiert

Und ich denke, das ist der grundlegende Unterschied zwischen Arrays und Strukturen. Eine Array-Variable ist ein Datenelement auf niedriger Ebene mit begrenzter Selbsterkenntnis. Grundsätzlich ist es ein Stück Speicher und eine Möglichkeit, sich darin zu indizieren.

Der Compiler kann also den Unterschied zwischen int a [10] und int b [20] nicht erkennen.

Strukturen haben jedoch nicht die gleiche Mehrdeutigkeit.

Scott Turley
quelle
3
Auf dieser Seite geht es darum, Arrays an Funktionen zu übergeben (was nicht möglich ist, es ist also nur ein Zeiger, was er meint, wenn er sagt, dass er seine Größe verliert). Das hat nichts mit dem Zuweisen von Arrays zu Arrays zu tun. Und nein, eine Array-Variable ist nicht nur "wirklich" ein Zeiger auf das erste Element, sondern ein Array. Arrays sind keine Zeiger.
GManNickG
Vielen Dank für den Kommentar, aber wenn ich diesen Abschnitt des Artikels lese, sagt er im Voraus, dass Arrays ihre eigene Größe nicht kennen, und verwendet dann ein Beispiel, in dem Arrays als Argumente übergeben werden, um diese Tatsache zu veranschaulichen. Wenn Arrays als Argumente übergeben werden, haben sie dann die Informationen über ihre Größe verloren oder hatten sie von Anfang an nie die Informationen. Letzteres habe ich angenommen.
Scott Turley
3
Der Compiler kann den Unterschied zwischen zwei Arrays unterschiedlicher Größe erkennen - versuchen Sie es mit Drucken im sizeof(a)Vergleich zu sizeof(b)oder Übergeben aan void f(int (&)[20]);.
Georg Fritzsche
Es ist wichtig zu verstehen, dass jede Arraygröße einen eigenen Typ darstellt. Die Regeln für die Parameterübergabe stellen sicher, dass Sie die "generischen" Funktionen des armen Mannes schreiben können, die Array-Argumente beliebiger Größe verwenden, auf Kosten der Notwendigkeit, die Größe separat zu übergeben. Wenn dies nicht der Fall wäre (und in C ++ können - und müssen! - Sie Referenzparameter für Arrays bestimmter Größe definieren - und müssen!), Benötigen Sie eine spezifische Funktion für jede unterschiedliche Größe, eindeutig Unsinn. Ich habe darüber in einem anderen Beitrag geschrieben .
Peter - Stellen Sie Monica
0

Ich weiß, jeder, der geantwortet hat, ist Experte für C / C ++. Aber ich dachte, das ist der Hauptgrund.

num2 = num1;

Hier versuchen Sie, die Basisadresse des Arrays zu ändern, was nicht zulässig ist.

und natürlich struct2 = struct1;

Hier wird das Objekt struct1 einem anderen Objekt zugewiesen.

nsivakr
quelle
Durch das Zuweisen von Strukturen wird schließlich das Array-Mitglied zugewiesen, was genau dieselbe Frage aufwirft. Warum ist das eine erlaubt und nicht das andere, wenn es in beiden Situationen ein Array ist?
GManNickG
1
Einverstanden. Der erste wird jedoch vom Compiler verhindert (num2 = num1). Der zweite wird vom Compiler nicht verhindert. Das macht einen großen Unterschied.
Nsivakr
Wenn Arrays zuweisbar num2 = num1wären , würden sie sich perfekt verhalten. Die Elemente von num2hätten den gleichen Wert wie das entsprechende Element von num1.
Juanchopanza
0

Ein weiterer Grund , keine weiteren Anstrengungen unternommen wurden Arrays in C , zu Rindfleisch ist wahrscheinlich , dass Array Zuweisung wäre nicht , dass nützlich. Obwohl dies in C leicht erreicht werden kann, indem es in eine Struktur eingeschlossen wird (und die Adresse der Struktur einfach zur weiteren Verarbeitung in die Adresse des Arrays oder sogar in die Adresse des ersten Elements des Arrays umgewandelt werden kann), wird diese Funktion selten verwendet. Ein Grund dafür ist, dass Arrays unterschiedlicher Größe nicht kompatibel sind, was die Vorteile der Zuweisung oder der damit verbundenen Übergabe an Funktionen nach Wert einschränkt.

Die meisten Funktionen mit Array-Parametern in Sprachen, in denen Arrays erstklassige Typen sind, werden für Arrays beliebiger Größe geschrieben. Die Funktion iteriert dann normalerweise über die angegebene Anzahl von Elementen, eine Information, die das Array bereitstellt. (In C besteht die Redewendung natürlich darin, einen Zeiger und eine separate Elementanzahl zu übergeben.) Eine Funktion, die ein Array mit nur einer bestimmten Größe akzeptiert, wird nicht so oft benötigt, sodass nicht viel übersehen wird. (Dies ändert sich, wenn Sie es dem Compiler überlassen können, eine separate Funktion für jede auftretende Arraygröße zu generieren, wie bei C ++ - Vorlagen. Dies ist der Grund, warum dies std::arraynützlich ist.)

Peter - Monica wieder einsetzen
quelle