C標準がconst-nessを再帰的に考慮する理由は何ですか?


9

C99標準は6.5.16:2で次のように述べています。

代入演算子は、左オペランドとして変更可能な左辺値を持つものとします。

6.3.2.1:1では:

変更可能な左辺値は、配列型がなく、不完全型がなく、const修飾型がなく、構造体または共用体である場合、メンバー(再帰的に、任意のメンバーを含む)がない左辺値ですまたは含まれるすべての集合体または共用体の要素)、const修飾型。

ではconst structconstフィールドのないものを考えてみましょう。

typedef struct S_s {
    const int _a;
} S_t;

標準では、次のコードは未定義の動作(UB)です。

S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;

これに関する意味上の問題structは、エンティティの宣言されたタイプ(S_t s1)から判断すると、囲んでいるエンティティ()は書き込み可能(読み取り専用ではない)と見なされるべきですが、標準の文言(2つの条項)によって書き込み可能と見なされるべきではありません。上部)constフィールドのため_a。規格では、割り当てを実際にUBであるとコードを読んでいるプログラマーに不明確にしていますstruct S_s ... S_t。これは、型の定義がないことを伝えることができないためです。

さらに、フィールドへの読み取り専用アクセスは、とにかく構文的にのみ適用されます。constnon-の一部のフィールドをconst struct実際に読み取り専用ストレージに配置する方法はありません。しかし、このような標準の表現はconst、これらのフィールドのアクセサープロシージャのフィールドの修飾子を故意にキャストするコードを非合法化します(Cの構造体のフィールドをconst修飾することは良い考えですか?):

(*)

#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);
}

したがって、なんらかの理由で、全体structを読み取り専用にするには、それを宣言するだけで十分です。const

const S_t s3;

しかし、全体structが読み取り専用ではない場合、それをw / oと宣言するだけでは不十分constです。

私がより良いと思うのは、次のいずれかです。

  1. フィールドをconst持つ非構造体の作成を制限し、そのconstような場合に診断を発行します。これにより、struct含まれている読み取り専用フィールドがそれ自体が読み取り専用であることは明らかです。
  2. 上記のコード(*)を規格に準拠させるためconstに、非const構造体に属するフィールドに書き込む場合の動作を定義します。

そうしないと、動作に一貫性がなくなり、理解が難しくなります。

では、Cスタンダードがconst-nessを再帰的に検討する理由は何ですか?


正直なところ、質問はありません。
Bart van Ingen Schenau

@BartvanIngenSchenauは、トピックの本文に記載された質問を本文の最後に追加するように編集しました
Michael Pankov

1
なぜ反対票か。
Michael Pankov

回答:


4

では、Cスタンダードがconst-nessを再帰的に検討する理由は何でしょうか?

タイプの観点からだけでは、そうしないと不健全になります(つまり、ひどく壊れて意図的に信頼できなくなります)。

これは、構造体で「=」が何を意味するかによるものです。これは再帰的な割り当てです。したがって、最終的にはs1._a = <value>「タイピングルールの内側」で発生することになります。標準がこれを「ネストされた」constフィールドに許可している場合、型システム定義に重大な矛盾を明示的な矛盾として追加します(そのconst機能によって、機能が無駄になり、信頼性がなくなるため、機能が破棄される可能性があります)。

あなたの解決策(1)は、私が理解しているconst限り、そのフィールドの1つがであるときはいつでも、構造全体を不必要に強制することですconst。このようにs1._b = bすると、を含む._b非constの非const フィールドでは不正になります。s1const a


上手。Cサウンドタイプシステムはほとんどありません(何年にもわたって互いにかみ合っているコーナーケースの束のようなものです)。さらに、aに割り当てを配置するもう1つの方法structmemcpy(s_dest, s_src, sizeof(S_t))です。そして、それが実際の実装方法であると確信しています。そして、そのような場合、既存の「型システム」でさえ、あなたがそうすることを禁止していません。
Michael Pankov

2
とても本当です。私は、Cの型システムが健全であることを示唆していなかったと思います。特定のセマンティクスを意図的に不健全にして意図的に無効にしているだけです。さらに、Cの型システムは強く強制されていませんが、その影響は暗黙的で追跡が難しい場合でも、違反する方法はしばしば明示的です(ポインター、間接アクセス、キャスト)。したがって、それらに違反するための明示的な「フェンス」があることは、定義自体に矛盾があるよりもよく知らせます。
チアゴ・シウバ

2

その理由は、読み取り専用フィールドは読み取り専用だからです。そこに大きな驚きはありません。

ROMへの配置が唯一の影響であると誤って想定しているため、非constフィールドが隣接している場合は、実際には不可能です。実際には、オプティマイザはconst式が書き込まれていないと想定し、それに基づいて最適化します。もちろん、非constエイリアスが存在する場合、その仮定は成り立ちません。

あなたの解決策(1)は、既存の合法的かつ合理的なコードを破ります。それは起こりません。あなたの解決策(2)はconst、メンバーの意味をかなり削除します。これは既存のコードを壊しませんが、理論的根拠に欠けているようです。


常にまたはを使用できるため、オプティマイザconstフィールドに書き込みを行わないことをオプティマイザ想定していない可能性があることを 90%確信しています。(1)フラグによって有効になる、少なくとも追加の警告として実装できます。(2)の理論的根拠は、まあ、正確に言うと、構造全体書き込み可能である場合、そのコンポーネントを書き込み不可と見なすことができないということです。memsetmemcpystruct
Michael Pankov

「フラグによって決定されるオプションの診断」は、規格が要求する固有の要件です。その上、フラグを設定しても既存のコードが壊れるので、事実上誰もフラグを気にすることはなく、行き止まりになります。(2)に関しては、6.3.2.1:1は正反対を指定します。1つのコンポーネントがそうであるときはいつでも、構造全体は書き込み不可です。ただし、他のコンポーネントは引き続き書き込み可能です。Cf. operator=メンバーに関しても定義するC ++。したがってoperator=、1つのメンバーがである場合は定義しませんconst。CとC ++はまだ互換性があります。
MSalters 2013

@constantius-メンバーのconstnessを意図的に回避するために何かを実行できるという事実は、オプティマイザがそのconstnessを無視する理由にはなりません。関数内で定数をキャストして、変更できるようにすることができます。ただし、呼び出しコンテキストのオプティマイザは、そうしないと想定することを許可されます。Constnessはプログラマーにとっては便利ですが、場合によってはオプティマイザーにとっての神の送信でもあります。
Michael Kohne

それでは、なぜ書き込み不可能な構造がieで上書きできるのmemcpyですか?他の理由については-わかりました、それは遺産ですが、なぜそもそもそのような方法で行われたのですか?
Michael Pankov

1
私はまだあなたのコメントmemcpyが正しいかどうか疑問に思っています。他の質問でのAFACIT John Bodeの引用は正しいです。コードはconst修飾オブジェクトに書き込まれるため、標準の不満ではなく、議論の終わりです。
MSalters 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.