Was ist der Grund für den C-Standard, die Konstanz rekursiv zu berücksichtigen?

9

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 structmit- constFeld.

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 constFeldes _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_tTyps zu sagen .

Darüber hinaus wird der schreibgeschützte Zugriff auf das Feld ohnehin nur syntaktisch erzwungen. Es gibt keine Möglichkeit, dass einige constFelder, die nicht vorhanden const structsind, wirklich schreibgeschützt gespeichert werden. Ein solcher Wortlaut von Standard verbietet jedoch den Code, der das constQualifikationsmerkmal 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 structals schreibgeschützt zu deklarierenconst

const S_t s3;

Aber damit ein Ganzes structnicht schreibgeschützt ist, reicht es nicht aus, es ohne zu deklarieren const.

Was ich für besser halte, ist entweder:

  1. Einschränkung der Erstellung von Nichtstrukturen constmit constFeldern und Ausgabe einer Diagnose in einem solchen Fall. Das würde deutlich machen, dass die structenthaltenden schreibgeschützten Felder selbst schreibgeschützt sind.
  2. Definieren des Verhaltens beim Schreiben in ein constFeld, das zu einer constNichtstruktur 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?

Michael Pankov
quelle
Um ehrlich zu sein, sehe ich dort keine Frage.
Bart van Ingen Schenau
@ BartvanIngenSchenau bearbeitet, um die im Thema am Ende des Körpers angegebene Frage hinzuzufügen
Michael Pankov
1
Warum das Downvote?
Michael Pankov

Antworten:

4

Was ist der Grund für C Standard, die Konstanz rekursiv zu betrachten, wie es heißt?

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" constFelder zulässt , fügt er eine ernsthafte Inkonsistenz in seiner Typsystemdefinition als expliziten Widerspruch hinzu (könnte das constFeature 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, constimmer dann zu sein, wenn sich eines ihrer Felder befindet const. Auf diese Weise s1._b = bwäre für ein Nicht-Konstantenfeld ._bin einem Nicht-Konstanten, s1das a enthält , unzulässig const a.

Thiago Silva
quelle
Gut. Chat 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 a structist memcpy(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.
Michael Pankov
2
Sehr richtig. Ich hoffe, ich habe nicht impliziert, dass das C-Typ-System solide ist, sondern nur, dass es absichtlich besiegt wird, wenn eine bestimmte Semantik absichtlich nicht stimmt. Obwohl das Typensystem von C nicht stark durchgesetzt wird, sind die Möglichkeiten, es zu verletzen, häufig explizit (Zeiger, indirekter Zugriff, Casts) - obwohl seine Auswirkungen häufig implizit und schwer zu verfolgen sind. Wenn Sie also explizite "Zäune" ​​haben, um sie zu durchbrechen, ist dies besser als ein Widerspruch in den Definitionen selbst.
Thiago Silva
2

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 constAusdrü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 constMitgliedern. Dies wird zwar den vorhandenen Code nicht beschädigen, es scheint jedoch keine Begründung zu geben.

MSalters
quelle
Ich bin zu 90% sicher, dass Optimierer nicht davon ausgehen, dass constFelder nicht beschrieben werden, da Sie immer memsetoder verwenden memcpykö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 Komponente structkann nicht beschreibbaren berücksichtigt werden , wenn die gesamte Struktur ist beschreibbar.
Michael Pankov
Eine "optionale Diagnose, die durch ein Flag bestimmt wird" wäre eine eindeutige Anforderung, die der Standard verlangen muss. Außerdem würde das Setzen des Flags immer noch den vorhandenen Code beschädigen, sodass sich praktisch niemand um das Flag kümmern würde und es eine Sackgasse wäre. In (2.) gibt 6.3.2.1:1 genau das Gegenteil an: Die gesamte Struktur ist nicht beschreibbar, wenn eine Komponente vorhanden ist. Jedoch andere Komponenten können noch beschreibbar sein. Vgl. C ++, das auch operator=in Bezug auf die Mitglieder definiert und daher nicht definiert, operator=wann ein Mitglied ist const. C und C ++ sind hier noch kompatibel.
MSalters
@constantius - Die Tatsache, dass Sie etwas tun können, um die Konstanz eines Mitglieds absichtlich zu umgehen, ist KEIN Grund für den Optimierer, diese Konstanz zu ignorieren. Sie können Konstanz innerhalb einer Funktion wegwerfen, so dass Sie Dinge ändern können. Der Optimierer im aufrufenden Kontext darf jedoch weiterhin davon ausgehen, dass dies nicht der Fall ist. Konstanz ist nützlich für den Programmierer, aber in einigen Fällen auch ein Geschenk für den Optimierer.
Michael Kohne
Warum darf dann eine nicht beschreibbare Struktur mit dh überschrieben werden memcpy? Was andere Gründe betrifft - ok, es ist Vermächtnis, aber warum wurde es überhaupt so gemacht?
Michael Pankov
1
Ich frage mich immer noch, ob Ihr Kommentar zu memcpyrichtig 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.
MSalters