Kiedy i do jakich celów należy używać słowa kluczowego const w C dla zmiennych?

58

Podczas sprawdzania mojego kodu tutajconst pojawił się problem użycia słowa kluczowego. Rozumiem, że służy do implementacji zachowania tylko do odczytu w zmiennych.

Jestem zdezorientowany, jakie są różne sytuacje, w których może to być przydatne.

  • Czy należy go używać ze względu na przejrzystość prototypów funkcji?
  • Czy powinien być stosowany jako środek bezpieczeństwa podczas tworzenia kodu?
  • Czy należy go stosować w zakresie różnych funkcji do deklarowania stałych czasu wykonywania?
  • Czy należy go w ogóle stosować?

Te pytania są tylko przykładami zamieszania, przed którym stoję. Ogólne zamieszanie jest takie

  • Kiedy constsłowo kluczowe powinno być używane w programowaniu C?
  • Jakie są różne rodzaje korzyści, które można uzyskać, używając tego słowa kluczowego w C?
  • Czy są jakieś wady używania constsłowa kluczowego?


Wskazano, że pytanie to może być zbyt szerokie ze względu na wszystkie te pytania w szczegółach mojego pytania. Chciałem tylko wyjaśnić, że te pytania mają na celu wyjaśnienie zamieszania związanego z głównym pytaniem.

Kiedy i do jakich celów należy używać słowa kluczowego const w C dla zmiennych?

Można go również sformułować jako

Właściwe użycie constsłowa kluczowego w C` z zaletami i wadami tego samego.

Aseem Bansal
źródło
Dla osób głosujących, które mają być zamknięte, proszę wyjaśnić, dlaczego nie należy do kategorii Specific issues with software development. Jestem dość specyficzny.
Aseem Bansal,
Zgaduję, że dzieje się tak, ponieważ zadałeś wiele pytań w tym samym poście i dlatego twoje pytanie należy do kategorii Zbyt szerokie: „Istnieje zbyt wiele możliwych odpowiedzi lub dobre odpowiedzi byłyby zbyt długie dla tego formatu. Proszę dodać szczegóły aby zawęzić zestaw odpowiedzi lub wyodrębnić problem, na który można odpowiedzieć w kilku akapitach. ”
Robert Harvey
1
@RobertHarvey Wszystkie te pytania mają wyjaśnić moje zamieszanie. Moje jedyne pytanie pozostaje tytułem tego pytania. Dobra odpowiedź na to pytanie obejmowałaby wszystkie te powiązane pytania. Pozostaje więc specific issue. Właściwe użycie constsłowa kluczowego w C` z zaletami i wadami tego samego.
Aseem Bansal
@RobertHarvey Czy teraz jest lepiej?
Aseem Bansal

Odpowiedzi:

64

Podczas przeglądania kodu stosuję następujące zasady:

  • Zawsze używaj constparametrów funkcji przekazywanych przez odniesienie, gdy funkcja nie modyfikuje (ani nie zwalnia) wskazanych danych.

    int find(const int *data, size_t size, int value);
  • Zawsze używaj constdla stałych, które w innym przypadku mogłyby zostać zdefiniowane za pomocą #define lub enum. W rezultacie kompilator może zlokalizować dane w pamięci tylko do odczytu (ROM) (chociaż linker jest często lepszym narzędziem do tego celu w systemach osadzonych).

    const double PI = 3.14;
  • Nigdy nie używaj const w prototypie funkcji dla parametru przekazywanego przez wartość . Nie ma to znaczenia i dlatego jest po prostu „hałasem”.

    // don't add const to 'value' or 'size'
    int find(const int *data, size_t size, const int value); 
  • W stosownych przypadkach użyj const volatilew miejscach, których program nie może zmienić, ale może się zmienić. Rejestry sprzętowe są tutaj typowym przypadkiem użycia, na przykład rejestr stanu odzwierciedlający stan urządzenia:

    const volatile int32_t *DEVICE_STATUS =  (int32_t*) 0x100;

Inne zastosowania są opcjonalne. Na przykład parametry funkcji w ramach implementacji funkcji można oznaczyć jako const.

// 'value' and 'size can be marked as const here
int find(const int *data, const size_t size, const int value)  
{
     ... etc

lub funkcja zwraca wartości lub obliczenia, które są uzyskiwane, a następnie nigdy się nie zmieniają:

char *repeat_str(const char *str, size_t n) 
{
    const size_t len = strlen(str);
    const size_t buf_size = 1 + (len * n);
    char *buf = malloc(buf_size);
    ...

Te zastosowania po constprostu wskazują, że nie zmienisz zmiennej; nie zmieniają sposobu ani miejsca przechowywania zmiennej. Kompilator może oczywiście zorientować się, że zmienna się nie zmienia, ale dodając constpozwalasz na wymuszenie tego. Może to pomóc czytelnikowi i zwiększyć bezpieczeństwo (chociaż jeśli twoje funkcje są wystarczająco duże lub skomplikowane, że robi to wielką różnicę, prawdopodobnie masz inne problemy). Edytuj - np. gęsto zakodowana funkcja 200-liniowa z zagnieżdżonymi pętlami i wieloma długimi lub podobnymi nazwami zmiennych, wiedząc, że niektóre zmienne nigdy się nie zmieniają, może znacznie ułatwić zrozumienie. Takie funkcje zostały źle zaprojektowane lub utrzymane.


Problemy z const. Prawdopodobnie usłyszysz termin „zatrucie ciągłe”. Dzieje się tak, gdy dodanie constdo parametru funkcji powoduje propagację „stałości”.

Edycja - zatrucie const: na przykład w funkcji:

int function_a(char * str, int n)
{
    ...
    function_b(str);
    ...
}

jeśli zmienimy strna const, musimy wtedy upewnić się, że fuction_brównież trwa const. I tak dalej, jeśli function_bprzekaże to strdalej function_c, itd. Jak można sobie wyobrazić, może to być bolesne, jeśli rozprzestrzeni się na wiele oddzielnych plików / modułów. Jeśli propaguje się do funkcji, której nie można zmienić (np. Biblioteki systemowej), konieczne jest rzutowanie. Więc przelewanie constsię w istniejącym kodzie może być przyczyną problemów. Jednak w nowym kodzie najlepiej jest constkonsekwentnie kwalifikować się w stosownych przypadkach.

Bardziej podstępnym problemem constjest to, że nie było w oryginalnym języku. Jako dodatek nie do końca pasuje. Na początek ma dwa znaczenia (jak w powyższych regułach, co oznacza „nie zamierzam tego zmieniać” i „tego nie można zmienić”). Ale co więcej, może być niebezpieczne. Na przykład skompiluj i uruchom ten kod i (w zależności od kompilatora / opcji) może się on zawiesić po uruchomieniu:

const char str[] = "hello world\n";
char *s = strchr(str, '\n');
*s = '\0';

strchrzwraca char*nie a const char*. Jak sama rozmowa jest parametr constmusi rzucać parametr wezwanie do char*. I w tym przypadku odrzuca to właściwość pamięci tylko do odczytu. Edycja: - dotyczy to ogólnie zmiennych w pamięci tylko do odczytu. Przez „ROM” rozumiem nie tylko fizyczną pamięć ROM, ale każdą pamięć chronioną przed zapisem, tak jak dzieje się w przypadku sekcji kodu programów działających na typowym systemie operacyjnym.

Wiele standardowych funkcji bibliotecznych zachowuje się w ten sam sposób, więc uważaj: gdy masz prawdziwe stałe (tj. Przechowywane w pamięci ROM), musisz bardzo uważać, aby nie stracić ich stałej.

William Morris
źródło
To naprawdę nie ma dwóch znaczeń. To po prostu oznacza, jak mówisz: „ Nie zamierzam tego zmieniać”. Na przykład posiadanie const volatilezmiennej jest całkowicie poprawne .
detly
2
Tak jest w przypadku parametrów funkcji, ale na przykład dla stałych w zakresie pliku, constzmienna jest naprawdę tylko do odczytu: constmówi „Tego nie można modyfikować”. Z const volatiledrugiej strony jest napisane: „Nie można tego zmienić, ale może się zmienić”. Dodam wzmiankę o składnikach lotnych do mojej odpowiedzi.
William Morris
Nie zgadzam się z twoim komentarzem, tylko z pomysłem, że jest to instrukcja dla kompilatora, aby umieścić go w ROM. Oznacza to pewnego rodzaju ochronę środowiska wykonawczego przed nieokreślonym zachowaniem (tj. Jakoś modyfikowanie const), co może prowadzić do nieporozumień, takich jak „dlaczego mogę zmienić tę constzmienną za pomocą constwskaźnika” (częste pytanie na SO i na każdym innym forum C !). Ogólnie jednak podoba mi się ta odpowiedź :)
detly
Masz rację, „Umieść to w pamięci ROM” jest niedokładne (choć zwięzłe :-) - Zamienię to na „Tego nie można modyfikować”, co jest bardziej dokładne. Dziękuję za komentarze.
William Morris
To świetny przegląd. Lubię to sformułować, który constdeklaruje zarówno dane tylko do odczytu, jak i widoki danych tylko do odczytu - różnica polega na tym, że „nie można tego zmienić” w porównaniu z „nie można tego zmienić”.
Jon Purdy,
8

Ogólnie w dowolnym języku programowania zaleca się jego użycie constlub równoważny modyfikator od tego czasu

  • Może wyjaśnić dzwoniącemu, że to, co przekazali, nie ulegnie zmianie
  • Potencjalne ulepszenia prędkości, ponieważ kompilator wie na pewno, że może pominąć pewne rzeczy, które są istotne tylko, jeśli parametr może się zmienić
  • Ochrona przed sobą przez przypadkową zmianę wartości
TheLQ
źródło
1

Tak, to w zasadzie odpowiedź TheLQ.

Jest to środek bezpieczeństwa dla programisty, więc nie modyfikujesz zmiennej i nie wywołujesz funkcji, które mogą ją modyfikować. W tablicy lub strukturze specyfikator const wskazuje, że wartości ich zawartości nie zostaną zmodyfikowane, a nawet kompilator nie pozwoli na to. Jednak nadal możesz łatwo zmienić wartość zmiennej za pomocą rzutowania.

W tym, co zwykle widzę, służy głównie do dodawania stałych wartości w kodzie i do wskazania, że ​​tablica lub struktura nie zostaną zmodyfikowane, jeśli wywołasz określoną funkcję. Ta ostatnia część jest ważna, ponieważ wywołując funkcję, która Modyfikuje tablicę lub strukturę, możesz chcieć zachować oryginalną wersję, więc utworzysz kopię zmiennej, a następnie przekażesz ją do funkcji. Jeśli tak nie jest, oczywiście nie potrzebujesz kopii, więc możesz na przykład zmienić,

int foo(Structure s);

do

int foo(const Structure * s);

i nie otrzymuję narzutu.

Wystarczy dodać, że C ma określone reguły ze specyfikatorem const. Na przykład,

int b = 1;
const int * a = &b;

to nie to samo co

int b = 1;
int * const a = &b;

Pierwszy kod nie pozwala na modyfikację. W drugim przypadku wskaźnik jest stały, ale jego zawartość nie jest, więc kompilator pozwoli ci powiedzieć * a = 3;bez błędu kompilatora, ale nie możesz uczynić aodniesienia do innej rzeczy.

lgarcia
źródło
0

Zgodnie z oświadczeniami TheLQ:

Podczas pracy z zespołem programistów deklarowanie constjest dobrym sposobem na wskazanie, że wymienionej zmiennej nie należy modyfikować, lub po prostu dla przypomnienia sobie w dużych projektach. Jest to przydatne w tym sensie i może uratować wiele bólów głowy.

David Otano
źródło