Norma C99 mówi w 6.5.16: 2:
Operator przypisania ma modyfikowalną wartość jako lewy operand.
oraz w 6.3.2.1:1:
Zmienna wartość jest wartością, która nie ma typu tablicowego, nie ma typu niekompletnego, nie ma typu stałego kwalifikowania, a jeśli jest strukturą lub związkiem, nie ma żadnego elementu (w tym, rekurencyjnie, żadnego elementu lub element wszystkich zawartych agregatów lub związków) o typie const.
Teraz rozważmy nie- const
struct
z const
pola.
typedef struct S_s {
const int _a;
} S_t;
Standardowo następujący kod jest niezdefiniowanym zachowaniem (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
Problem semantyczny polega na tym, że encja zamykająca ( struct
) powinna być uważana za zapisywalną (nie tylko do odczytu), sądząc po zadeklarowanym typie encji ( S_t s1
), ale nie powinna być uważana za zapisywalną na podstawie sformułowania standardowego (2 klauzule na górze) z powodu const
pola _a
. Standard sprawia, że dla programisty czytającego kod nie jest jasne, że przypisanie jest w rzeczywistości UB, ponieważ nie można powiedzieć, że bez definicji struct S_s ... S_t
typu.
Co więcej, dostęp tylko do odczytu do pola jest i tak egzekwowany tylko syntaktycznie. Nie ma możliwości, aby niektóre const
pola nie- const
struct
tak naprawdę zostały umieszczone w pamięci tylko do odczytu. Ale takie sformułowanie standardu zakazuje kodu, który celowo odrzuca const
kwalifikator pól w procedurach akcesyjnych tych pól, podobnie jak ( Czy dobrym pomysłem jest stałe kwalifikowanie pól struktury w 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);
}
Z jakiegoś powodu, aby całość struct
była tylko do odczytu, wystarczy ją zadeklarowaćconst
const S_t s3;
Ale aby całość struct
nie była tylko do odczytu, nie wystarczy zadeklarować jej brak const
.
To, co moim zdaniem byłoby lepsze, to:
- Ograniczanie tworzenia niestruktur za
const
pomocąconst
pól i w takim przypadku wydawanie diagnozy. To by wyjaśniało, żestruct
zawierające pola tylko do odczytu jest tylko do odczytu. - Zdefiniowanie zachowania w przypadku zapisu w
const
polu należącym doconst
struktury innej niż strukturalna w celu dostosowania powyższego kodu (*) do standardu.
W przeciwnym razie zachowanie nie będzie spójne i trudne do zrozumienia.
Jaki jest zatem powód, dla którego C Standard rozważa const
rekurencyjność, jak to ujmuje?
Odpowiedzi:
Z samej perspektywy typowej, niezrobienie tego byłoby niewłaściwe (innymi słowy: strasznie złamane i celowo niewiarygodne).
A to dlatego, że „=” oznacza strukturę: jest to zadanie rekurencyjne. Wynika z tego, że w końcu
s1._a = <value>
zdarza się „wewnątrz zasad pisania”. Jeśli norma zezwala na to w przypadkuconst
pól „zagnieżdżonych” , dodanie poważnej niespójności w definicji systemu typów jako wyraźnej sprzeczności (może równie dobrze odrzucić tęconst
funkcję, ponieważ stała się bezużyteczna i zawodna z samej definicji).Wasze rozwiązanie (1), o ile rozumiem, niepotrzebnie zmusza całą strukturę do działania,
const
ilekroć znajduje się jedno z jej pólconst
. W ten sposóbs1._b = b
byłoby niezgodne z prawem dla._b
pola niestałego na niestałyms1
zawierającymconst a
.źródło
C
ledwo ma system dźwiękowy (bardziej jak wiązka narożnych skrzynek przymocowanych do siebie na przestrzeni lat). Poza tym innym sposobem przypisania przypisania do astruct
jestmemcpy(s_dest, s_src, sizeof(S_t))
. I jestem prawie pewien, że jest to faktyczny sposób implementacji. W takim przypadku nawet istniejący „system typów” nie zabrania tego.Powodem jest to, że pola tylko do odczytu są tylko do odczytu. Nie ma w tym żadnej wielkiej niespodzianki.
Błędnie zakładasz, że jedynym efektem jest umieszczenie w pamięci ROM, co w istocie jest niemożliwe, gdy istnieją sąsiednie pola nie stałe. W rzeczywistości optymalizatorzy mogą zakładać, że
const
wyrażenia nie są zapisywane, i na ich podstawie optymalizować. Oczywiście założenie to nie obowiązuje, gdy istnieją nie-aliasy.Twoje rozwiązanie (1) łamie obowiązujący kodeks prawny i rozsądny. To się nie stanie. Twoje rozwiązanie (2) praktycznie usuwa znaczenie
const
członków. Chociaż nie zepsuje to istniejącego kodu, wydaje się, że nie ma uzasadnienia.źródło
const
pola nie są zapisane, ponieważ zawsze można użyćmemset
lubmemcpy
, a nawet byłoby to zgodne ze standardem. (1) można zaimplementować jako co najmniej dodatkowe ostrzeżenie, włączane przez flagę. Uzasadnieniem (2) jest to, cóż, dokładnie - nie ma mowy, aby element składowystruct
mógł zostać uznany za niemożliwy do zapisu, gdy cała struktura jest zapisywalna.operator=
członków, a zatem nie określa,operator=
kiedy jest jednym członkiemconst
. C i C ++ są tutaj nadal kompatybilne.memcpy
? Jeśli chodzi o inne powody - ok, to jest dziedzictwo, ale dlaczego tak się stało?memcpy
jest słuszny. UWAŻAJ, że cytat Johna Bode'a w twoim drugim pytaniu jest słuszny: twój kod zapisuje obiekt o stałym parametrze i dlatego NIE jest standardową skargą, zakończeniem dyskusji.