Apa alasan standar C untuk mempertimbangkan keteguhan secara rekursif?

9

Standar C99 mengatakan dalam 6.5.16: 2:

Operator penugasan harus memiliki nilai yang dapat dimodifikasi sebagai operan kirinya.

dan di 6.3.2.1:1:

Lvalue yang dapat dimodifikasi adalah lvalue yang tidak memiliki tipe array, tidak memiliki tipe tidak lengkap, tidak memiliki tipe const-kualifikasi, dan jika itu adalah struktur atau gabungan, tidak memiliki anggota (termasuk, secara rekursif, setiap anggota atau elemen dari semua agregat atau serikat yang terkandung) dengan tipe const-kualifikasi.

Sekarang, mari kita pertimbangkan non- const structdengan constbidang.

typedef struct S_s {
    const int _a;
} S_t;

Secara standar, kode berikut adalah perilaku tidak terdefinisi (UB):

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

Masalah semantik dengan hal ini adalah bahwa entitas terlampir ( struct) harus dianggap dapat ditulis (non-read-only), dilihat dari tipe entitas yang dinyatakan ( S_t s1), tetapi tidak boleh dianggap dapat ditulis oleh kata-kata standar (2 klausa di atas) karena constbidang _a. Standar membuatnya tidak jelas bagi seorang programmer yang membaca kode bahwa tugas itu sebenarnya adalah UB, karena tidak mungkin untuk mengatakan bahwa tanpa definisi struct S_s ... S_ttipe.

Selain itu, akses baca-saja ke bidang ini hanya ditegakkan secara sintaksis pula. Tidak mungkin beberapa constbidang non const struct-benar-benar ditempatkan untuk penyimpanan read-only. Tetapi kata-kata standar seperti itu melarang kode yang dengan sengaja membuang constkualifikasi bidang dalam prosedur aksesor bidang ini, seperti itu ( Apakah itu ide yang baik untuk menyamakan kualifikasi bidang struktur di C? ):

(*)

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

Jadi, untuk beberapa alasan, agar keseluruhan structdapat dibaca - hanya cukup untuk menyatakannyaconst

const S_t s3;

Tetapi untuk keseluruhan structmenjadi non-read-only saja tidak cukup untuk menyatakannya tanpa const.

Apa yang saya pikir akan lebih baik, adalah:

  1. Untuk membatasi penciptaan non- conststruktur dengan constbidang, dan mengeluarkan diagnostik dalam kasus seperti itu. Itu akan memperjelas bahwa structbidang yang hanya baca hanya baca itu sendiri.
  2. Untuk mendefinisikan perilaku dalam kasus menulis ke constbidang milik non- conststruct untuk membuat kode di atas (*) sesuai dengan Standar.

Kalau tidak, tingkah lakunya tidak konsisten dan sulit dipahami.

Jadi, apa alasan C Standard mempertimbangkan const-ness secara rekursif, seperti yang dikatakannya?

Michael Pankov
sumber
Sejujurnya, saya tidak melihat pertanyaan di sana.
Bart van Ingen Schenau
@BartvanIngenSchenau diedit untuk menambahkan pertanyaan yang dinyatakan dalam topik di akhir tubuh
Michael Pankov
1
Mengapa downvote?
Michael Pankov

Jawaban:

4

Jadi, apa alasan bagi C Standard untuk mempertimbangkan keteguhan secara rekursif, seperti yang dikatakannya?

Dari perspektif tipe saja, tidak melakukan hal itu akan tidak sehat (dengan kata lain: sangat rusak dan sengaja tidak dapat diandalkan).

Dan itu karena apa yang berarti "=" pada struct: itu adalah tugas rekursif. Oleh karena itu, pada akhirnya Anda mengalami s1._a = <value>"di dalam aturan mengetik". Jika standar memungkinkan ini untuk constbidang "bersarang" , itu menambah inkonsistensi serius dalam definisi sistem tipenya sebagai kontradiksi eksplisit (mungkin juga membuang constfitur, karena hanya menjadi tidak berguna dan tidak dapat diandalkan dengan definisi yang sangat).

Solusi Anda (1), sejauh yang saya mengerti, tidak perlu memaksa seluruh struktur untuk menjadi constsetiap kali salah satu bidangnya const. Dengan cara ini, s1._b = bakan ilegal untuk bidang non-const ._bpada non-const yang s1mengandung a const a.

Thiago Silva
sumber
Baik. Cnyaris tidak memiliki sistem jenis suara (lebih seperti sekelompok kasus sudut diikat satu sama lain selama bertahun-tahun). Selain itu, cara lain untuk menempatkan tugas ke structadalah memcpy(s_dest, s_src, sizeof(S_t)). Dan saya cukup yakin itulah cara sebenarnya penerapannya. Dan dalam kasus seperti itu bahkan "sistem tipe" yang ada tidak melarang Anda melakukan itu.
Michael Pankov
2
Sangat benar. Saya harap saya tidak menyiratkan bahwa sistem tipe C adalah suara, hanya itu sengaja membuat semantik tertentu tidak sehat sengaja mengalahkannya. Selain itu, meskipun sistem tipe C tidak ditegakkan dengan kuat, cara-cara untuk menerobosnya seringkali eksplisit (pointer, akses tidak langsung, gips) - walaupun efeknya sering kali implisit dan sulit dilacak. Dengan demikian, memiliki "pagar" eksplisit untuk menerobosnya memberi informasi lebih baik daripada memiliki kontradiksi dalam definisi itu sendiri.
Thiago Silva
2

Alasannya adalah bahwa bidang hanya baca adalah hanya baca. Tidak ada kejutan besar di sana.

Anda keliru dengan asumsi bahwa satu-satunya efek adalah pada penempatan di ROM, yang memang tidak mungkin ketika ada bidang non-const yang berdekatan. Pada kenyataannya, pengoptimal mungkin menganggap constekspresi tidak ditulis untuk, dan mengoptimalkan berdasarkan itu. Tentu saja asumsi itu tidak berlaku ketika alias non-const ada.

Solusi Anda (1) melanggar kode hukum dan akal yang ada. Itu tidak akan terjadi. Solusi Anda (2) cukup banyak menghilangkan makna constpada anggota. Meskipun ini tidak akan merusak kode yang ada, tampaknya tidak memiliki alasan.

MSalters
sumber
Saya 90% yakin pengoptimal mungkin tidak menganggap bahwa constbidang tidak ditulis untuk, karena Anda selalu dapat menggunakan memsetatau memcpy, dan itu bahkan akan sesuai dengan Standar. (1) dapat diimplementasikan sebagai, setidaknya, peringatan tambahan, diaktifkan oleh sebuah bendera. (2) 's alasan adalah bahwa, baik, persis - tidak ada cara komponen structdapat dianggap non-ditulis ketika seluruh struktur adalah ditulis.
Michael Pankov
"Diagnostik opsional ditentukan oleh bendera" akan menjadi persyaratan unik bagi Standar untuk diminta. Selain itu, pengaturan bendera masih akan merusak kode yang ada, jadi pada dasarnya tidak ada yang mau repot dengan bendera dan itu akan menjadi jalan buntu. Adapun (2), 6.3.2.1:1 menentukan secara persis sebaliknya: seluruh struktur tidak dapat ditulisi setiap kali satu komponen. Namun, komponen lain mungkin masih dapat ditulisi. Lih C ++ yang mendefinisikan juga operator=dalam hal anggota, dan karena itu tidak menentukan operator=kapan satu anggota const. C dan C ++ masih kompatibel di sini.
MSalters
@constantius - Fakta bahwa Anda BISA melakukan sesuatu untuk dengan sengaja mengatasi ketegaran anggota BUKAN alasan pengoptimal untuk mengabaikan keteguhan itu. Anda BISA membuang ketegaran dalam suatu fungsi, memungkinkan Anda untuk mengubah hal-hal. Tetapi pengoptimal dalam konteks panggilan masih diizinkan untuk menganggap Anda tidak akan melakukannya. Constness berguna untuk programmer, tetapi juga mengirim dewa untuk optimizer dalam beberapa kasus.
Michael Kohne
Lalu mengapa struktur yang tidak bisa ditulisi diizinkan ditimpa dengan kata lain memcpy? Adapun alasan lain - ok, itu warisan, tetapi mengapa itu dilakukan sedemikian rupa di tempat pertama?
Michael Pankov
1
Saya masih bertanya-tanya apakah komentar Anda tentang memcpyitu benar. AFACIT Kutipan John Bode dalam pertanyaan Anda yang lain benar: kode Anda menulis ke objek yang memenuhi syarat dan karenanya BUKAN keluhan standar, akhir diskusi.
MSalters