2014-12-21 24 views
20

Użycie const ze wskaźnikiem może uniemożliwić zmianę wskazanego punktu przez dereferencję za pomocą odpowiedniego wskaźnika. Ale dlaczego nie mogę zmienić tego, na co wskaźnik nie wskazuje bezpośrednio?const i wskaźniki w C

Na przykład:

int a = 3; 
const int* ptr = &a; 
*ptr = 5; 

nie będzie skompilować. Ale dlaczego nie można kompilować? Nie zmieniam tego, na co wskazuje wskaźnik.
Więc musimy powiedzieć, że używanie const ze wskaźnikiem w taki sposób nie tylko nie powoduje modyfikacji tego, na co wskazuje wskaźnik (przez dereferencję wskaźnika), ale także czegokolwiek innego, do którego adres dostajemy za pomocą wskaźnika?

Wiem, że w tym przykładzie próbuję uzyskać dostęp do nie przydzielonej pamięci, ale to tylko ze względu na dyskusję.

+0

Czy spodziewałeś się, że kompilator przyjmie, że tylko pierwsze bajty 'sizeof (int)' to 'const'? Bardziej sensowne jest, że kompilator zakłada, że ​​dowolny adres pamięci uzyskany za pomocą 'ptr' jest adresem tylko do odczytu (" fizycznie "tylko do odczytu lub przynajmniej tylko do odczytu w" perspektywie "funkcji, która używa' ptr '). –

Odpowiedz

25

ptr +2 ma po prostu ten sam typ co ptr czyli jest wskaźnikiem do obiektu const.

Arytmetyka wskaźnikowa zakłada, że ​​obiekt, który jest wskazany, jest tablicą o tym samym typie podstawowym. Ten typ obejmuje kwalifikację const.

+0

Tak samo jak int + float = float, więc const [type] * + some value = const [type] *, right? – Root149

+0

@ Root149, coś podobnego, tak. Dodanie dowolnego typu liczby całkowitej do typu wskaźnika daje ten sam typ wskaźnika. –

+0

Tylko z rozpadem typów wskaźników (wszystko zostaje spłaszczone do wskaźników, gdy zaangażowane są wskaźniki, w tym tablice), gdzie promowane są normalne typy całkowite do typu, który może reprezentować wszystko. – Blindy

13

Niemodyfikowalność wprowadzona przez const zależy od tego, gdzie jest napisane const.

Jeśli piszesz

const int * ptr = &a; 

(lub int const * ptr = &a;), przy czym const odnosi się do wskazywanego, więc przy użyciu wskaźnika na piśmie do przeznaczenia jest zabronione.

(OTOH, jeśli wou napisał

int * const ptr = &a; 

nie można modyfikować ptr.)

w Twoim przypadku, wszystko udziałem piśmie do przeznaczenia jest zabronione.

Obejmuje *ptr i ptr[0] (które są równoważne), lecz także wszystkim obejmującej zmianę adresu docelowego, takiego jak *(ptr + 2) lub ptr[2].

+5

(i zauważ, że 'const int *' i 'int const *' to to samo - wskaźnik do const, a nie wskaźnik const.) – Mat

+0

również zwrócić uwagę, że biorąc pod uwagę "' int * const ptr = & a; '", możesz napisać "' * ptr = 5; '", ale nie "' ++ ptr' ". Dobry opis zasad znajduje się w odpowiedzi na http://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-int-const - pierwszej linii odpowiedź - "Odczytaj wstecz" - podsumowuje to pięknie. – frasnian

+0

@frasnian To jest zasadniczo to, co powiedziałem - "++ ptr" jest objęte przez "nie można modyfikować' ptr' ". – glglgl

4

Ponieważ wskaźnik może być również używany jako tablica (pomyśl o argv), kompilator ogranicza każdy dostęp, w który zaangażowany jest wskaźnik. W ten sposób cała tablica jest tylko do odczytu.

+0

Cóż, argv jest po prostu wskaźnikiem, ponieważ zapis char * argv [] jest równoważny char ** argv. po prostu tworzymy iluzję tablicy. – Root149

+0

@ Root149 Macierz nie ma znaczenia, jeśli chodzi o dostęp do niej, ponieważ dostęp działa mimo to za pomocą wskaźników. – glglgl

+1

@ Root149: Racja. I z powodu tej iluzji, kompilator nie wie, czy twoja wskazówka jest również takim "złudzeniem". Może być używany jako tablica, np. 'Argv', a zatem każdy dostęp do zapisu musi być ograniczony (bez względu na to, czy jest to' ptr [0] 'lub' ptr [100] '). –

3

Pomyślmy o typie wyrażeń.

const int* ptr = &a; 

Rodzaj ptr jest const int*.

Tak więc typ *ptr to const int. Nie można modyfikować.

Typ (ptr + 2) to nadal const int*, więc typ *(ptr + 2) to const int, który ponownie nie jest modyfikowalny.

9

Chociaż inne odpowiedzi wyjaśniają techniczne przyczyny, dla których to nie działa, chciałbym przedstawić bardziej ogólny powód: to jedyna rzecz, która ma sens.

Problem polega na tym, że nie ma ogólnego sposobu, aby kompilator zadecydował, czy p + something jest taki sam jak p, czy nie, ponieważ something może być dowolnie złożony. Reguła jak „wartości wskazywanej przez p i p + 0 są niezmienne, ale inne wartości mogą nadal być modyfikowana” nie może być sprawdzane w czasie kompilacji: wyobraź sobie, gdybyś napisał:

*(p + very complex expression) = ... 

powinna kompilator być w stanie dowiedzieć się, czy very complex expression wynosi zero? A co z tym, co należy zrobić w tym przypadku? Co powinien zrobić kompilator?

Jedynym rozsądnym wyborem było to, aby dowolna wartość dostępna poprzez p stała się niemożliwa do modyfikacji.

+0

To zachowanie nie jest spowodowane żadną niejednoznacznością przy podejmowaniu decyzji przez kompilator. Jest to prosta i prawidłowa konsekwencja reguł systemu typu. –

+0

@ JamesT.Huggett: Nie jestem pewien, co masz na myśli przez "niejednoznaczność w podejmowaniu decyzji przez kompilator"; to prawda, że ​​zachowanie jest konsekwencją reguły pisania, a moja odpowiedź próbuje wyjaśnić, dlaczego reguły systemu typu zostały zaprojektowane w ten sposób. –