Der C99-Standard sagt in 6.5.16: 2:
Ein Zuweisungsoperator muss einen modifizierbaren Wert als linken Operanden haben.
und in 6.3.2.1:1:
Ein modifizierbarer Wert ist ein Wert, der keinen Array-Typ hat, keinen unvollständigen Typ hat, keinen const-qualifizierten Typ hat und, wenn es sich um eine Struktur oder Vereinigung handelt, kein Mitglied hat (einschließlich rekursiv eines Mitglieds) oder Element aller enthaltenen Aggregate oder Gewerkschaften) mit einem const-qualifizierten Typ.
Betrachten wir nun ein Nicht- const
struct
mit- const
Feld.
typedef struct S_s {
const int _a;
} S_t;
Standardmäßig ist der folgende Code undefiniertes Verhalten (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
Das semantische Problem dabei ist, dass die einschließende Entität ( struct
) als beschreibbar (nicht schreibgeschützt) betrachtet werden sollte, gemessen am deklarierten Typ der Entität ( S_t s1
), aber nicht als beschreibbar durch den Wortlaut des Standards (die 2 Klauseln) oben) wegen des const
Feldes _a
. Der Standard macht es für einen Programmierer, der den Code liest, unklar, dass die Zuweisung tatsächlich eine UB ist, da es unmöglich ist, dies ohne die Definition des struct S_s ... S_t
Typs zu sagen .
Darüber hinaus wird der schreibgeschützte Zugriff auf das Feld ohnehin nur syntaktisch erzwungen. Es gibt keine Möglichkeit, dass einige const
Felder, die nicht vorhanden const
struct
sind, wirklich schreibgeschützt gespeichert werden. Ein solcher Wortlaut von Standard verbietet jedoch den Code, der das const
Qualifikationsmerkmal von Feldern in Accessor-Prozeduren dieser Felder absichtlich wegwirft ( Ist es eine gute Idee, die Strukturfelder in C zu qualifizieren? ):
(*)
#include <stdlib.h>
#include <stdio.h>
typedef struct S_s {
const int _a;
} S_t;
S_t *
create_S(void) {
return calloc(sizeof(S_t), 1);
}
void
destroy_S(S_t *s) {
free(s);
}
const int
get_S_a(const S_t *s) {
return s->_a;
}
void
set_S_a(S_t *s, const int a) {
int *a_p = (int *)&s->_a;
*a_p = a;
}
int
main(void) {
S_t s1;
// s1._a = 5; // Error
set_S_a(&s1, 5); // OK
S_t *s2 = create_S();
// s2->_a = 8; // Error
set_S_a(s2, 8); // OK
printf("s1.a == %d\n", get_S_a(&s1));
printf("s2->a == %d\n", get_S_a(s2));
destroy_S(s2);
}
Aus irgendeinem Grund reicht es aus, ein Ganzes struct
als schreibgeschützt zu deklarierenconst
const S_t s3;
Aber damit ein Ganzes struct
nicht schreibgeschützt ist, reicht es nicht aus, es ohne zu deklarieren const
.
Was ich für besser halte, ist entweder:
- Einschränkung der Erstellung von Nichtstrukturen
const
mitconst
Feldern und Ausgabe einer Diagnose in einem solchen Fall. Das würde deutlich machen, dass diestruct
enthaltenden schreibgeschützten Felder selbst schreibgeschützt sind. - Definieren des Verhaltens beim Schreiben in ein
const
Feld, das zu einerconst
Nichtstruktur gehört, um den obigen Code (*) mit dem Standard kompatibel zu machen.
Ansonsten ist das Verhalten nicht konsistent und schwer zu verstehen.
Was ist der Grund für C Standard, const
-ness rekursiv zu betrachten , wie es heißt?
Antworten:
Allein aus typischer Sicht wäre es nicht sinnvoll, dies nicht zu tun (mit anderen Worten: schrecklich gebrochen und absichtlich unzuverlässig).
Und das liegt daran, was "=" für eine Struktur bedeutet: Es ist eine rekursive Zuweisung. Daraus folgt, dass irgendwann ein
s1._a = <value>
Ereignis "innerhalb der Schreibregeln" auftritt. Wenn der Standard dies für "verschachtelte"const
Felder zulässt , fügt er eine ernsthafte Inkonsistenz in seiner Typsystemdefinition als expliziten Widerspruch hinzu (könnte dasconst
Feature genauso gut wegwerfen , da es durch seine Definition einfach nutzlos und unzuverlässig geworden ist).Ihre Lösung (1) zwingt, soweit ich es verstehe, die gesamte Struktur unnötig dazu,
const
immer dann zu sein, wenn sich eines ihrer Felder befindetconst
. Auf diese Weises1._b = b
wäre für ein Nicht-Konstantenfeld._b
in einem Nicht-Konstanten,s1
das a enthält , unzulässigconst a
.quelle
C
hat kaum ein Soundsystem (eher wie ein Haufen Eckkästen, die im Laufe der Jahre aneinander geschnallt wurden). Außerdem auf die andere Art und Weise die Zuordnung gestellt astruct
istmemcpy(s_dest, s_src, sizeof(S_t))
. Und ich bin mir ziemlich sicher, dass es die tatsächliche Art und Weise ist, wie es implementiert wird. Und in einem solchen Fall verbietet Ihnen selbst das vorhandene "Typsystem" dies nicht.Der Grund ist, dass schreibgeschützte Felder schreibgeschützt sind. Keine große Überraschung.
Sie gehen fälschlicherweise davon aus, dass der einzige Effekt die Platzierung im ROM ist, was in der Tat unmöglich ist, wenn benachbarte nicht konstante Felder vorhanden sind. In der Realität können Optimierer davon ausgehen, dass
const
Ausdrücke nicht geschrieben wurden, und auf dieser Grundlage optimieren. Natürlich gilt diese Annahme nicht, wenn nicht konstante Aliase existieren.Ihre Lösung (1) verstößt gegen den bestehenden rechtlichen und angemessenen Kodex. Das wird nicht passieren. Ihre Lösung (2) entfernt so ziemlich die Bedeutung von
const
Mitgliedern. Dies wird zwar den vorhandenen Code nicht beschädigen, es scheint jedoch keine Begründung zu geben.quelle
const
Felder nicht beschrieben werden, da Sie immermemset
oder verwendenmemcpy
können und dies sogar dem Standard entspricht. (1) kann zumindest als zusätzliche Warnung implementiert werden, die durch ein Flag aktiviert wird. (2) 's Grund dafür ist , dass, na ja, genau - es gibt keine Möglichkeit , eine Komponentestruct
kann nicht beschreibbaren berücksichtigt werden , wenn die gesamte Struktur ist beschreibbar.operator=
in Bezug auf die Mitglieder definiert und daher nicht definiert,operator=
wann ein Mitglied istconst
. C und C ++ sind hier noch kompatibel.memcpy
? Was andere Gründe betrifft - ok, es ist Vermächtnis, aber warum wurde es überhaupt so gemacht?memcpy
richtig ist. AFACIT John Bodes Zitat in Ihrer anderen Frage ist richtig: Ihr Code schreibt in ein const-qualifiziertes Objekt und ist daher KEINE Standardbeschwerde, Ende der Diskussion.