Co ciekawe, jest to prawie dokładnie to samo pytanie, co stackoverflow.com/questions/1637332/static-const-vs-define . Jedyna różnica polega na tym, że to pytanie dotyczy C ++, a to dotyczy C. Ponieważ moja odpowiedź była specyficzna dla C ++, mówię, że sprawia, że nie są one identyczne, ale inni mogą się nie zgadzać.
TED,
53
Zdecydowanie nie identyczny. Istnieje wiele obszarów, w których C ++ zezwala na składnię C ze względu na kompatybilność. W takich przypadkach pytania typu „jaki jest najlepszy sposób na X” będą miały różne odpowiedzi w C ++. Np. Inicjalizacja obiektu.
Jak to się nie opiera na opiniach? Każdy z nich ma inny cel
Sam Hammamy
1
@RobertSsupportsMonicaCellio, Tak. Dziękuję za informację
Vijay
Odpowiedzi:
690
To zależy od tego, dla czego potrzebujesz wartości. Ty (i wszyscy dotychczas) pominąłeś trzecią opcję:
static const int var = 5;
#define var 5
enum { var = 5 };
Ignorując problemy dotyczące wyboru nazwy, a następnie:
Jeśli chcesz przekazać wskaźnik, musisz użyć (1).
Ponieważ (2) jest najwyraźniej opcją, nie musisz przekazywać wskaźników.
Zarówno (1), jak i (3) mają symbol w tablicy symboli debugera - co ułatwia debugowanie. Bardziej prawdopodobne jest, że (2) nie będzie miał symbolu, co spowoduje, że zastanawiasz się, co to jest.
(1) nie może być stosowany jako wymiar dla tablic o zasięgu globalnym; zarówno (2), jak i (3) mogą.
(1) nie może być stosowany jako wymiar dla tablic statycznych w zakresie funkcji; zarówno (2), jak i (3) mogą.
W wersji C99 wszystkie z nich mogą być używane do tablic lokalnych. Technicznie, użycie (1) oznaczałoby użycie VLA (tablica o zmiennej długości), chociaż wymiar, do którego odnosi się „var”, byłby oczywiście ustalony na rozmiar 5.
(1) nie można go używać w miejscach takich jak instrukcje zamiany; zarówno (2), jak i (3) mogą.
(1) nie można użyć do inicjalizacji zmiennych statycznych; zarówno (2), jak i (3) mogą.
(2) może zmienić kod, którego nie chciałeś zmienić, ponieważ jest on używany przez preprocesor; zarówno (1), jak i (3) nie będą miały nieoczekiwanych skutków ubocznych.
Możesz wykryć, czy (2) zostało ustawione w preprocesorze; ani (1) ani (3) na to nie pozwalają.
Tak więc w większości kontekstów wolą „wyliczanie” niż alternatywy. W przeciwnym razie pierwszy i ostatni punkt kuli będą prawdopodobnie czynnikami kontrolującymi - i musisz się bardziej zastanowić, jeśli chcesz zaspokoić oba naraz.
Gdybyś pytał o C ++, używałbyś opcji (1) - stałej statycznej - za każdym razem.
fantastyczna lista! Wadą enumjest to, że są one zaimplementowane jako int([C99] 6.7.2.2/3). A #definepozwala określić niepodpisane i długie z Uoraz Lsufiksy oraz constpozwala podać typ. enummoże powodować problemy z konwersjami zwykłego typu.
Gauthier
37
(2) ludzie ZAWSZE narzekają na bezpieczeństwo typu. Nigdy nie rozumiem, dlaczego nie użyć po prostu „#define var ((int) 5)” i przeszkadzać, że masz bezpieczeństwo typu z definicją.
Ingo Blackman
6
@RedX: musiałbyś znajdować się w bardzo osobliwym środowisku, aby mieć problem z przestrzenią. To powiedziawszy, ani enumnie #definewykorzystuje dodatkowej przestrzeni per se. Wartość pojawi się w kodzie obiektowym jako część instrukcji, a nie zostanie przydzielona pamięć w segmencie danych, w stercie lub na stosie. Będziesz miał trochę miejsca przydzielonego na static const int, ale kompilator może go zoptymalizować, jeśli nie weźmiesz adresu.
Jonathan Leffler,
15
Kolejny „głos” na enums (i static const): nie można ich zmienić. a definemoże być #undefined, gdzie enumi static constsą ustalone na podaną wartość.
Daan Timmer
15
@QED: Nie, dziękuję. Prosta stała jest bezpieczna poza nawiasami. Lub pokaż mi, w jaki sposób program, który można zgodnie z prawem oczekiwać, że się skompiluje, zostałby zmieniony przez brak 5 w nawiasach. Jeśli byłby to argument makra w stylu funkcji lub gdyby w wyrażeniu były jakieś operatory, to słusznie obwiniałbyś mnie, gdybym nie zawarł nawiasów. Ale tutaj tak nie jest.
Jonathan Leffler
282
Ogólnie rzecz biorąc:
staticconst
Ponieważ szanuje zakres i jest bezpieczny dla typu.
Jedyne zastrzeżenie, jakie widziałem: jeśli chcesz, aby zmienna była możliwie zdefiniowana w wierszu poleceń. Nadal istnieje alternatywa:
#ifdef VAR // Very bad name, not long enough, too general, etc..staticintconst var = VAR;#elsestaticintconst var =5;// default value#endif
O ile to możliwe, zamiast makr / elipsy, używaj bezpiecznej dla danego typu alternatywy.
Jeśli naprawdę POTRZEBUJESZ użyć makra (na przykład chcesz __FILE__lub __LINE__), to lepiej nazwij makro BARDZO ostrożnie: w swojej konwencji nazewnictwa Boost zaleca wszystkie wielkie litery, zaczynając od nazwy projektu (tutaj BOOST_ ), przeglądając bibliotekę, zauważysz, że po niej (na ogół) następuje nazwa określonego obszaru (biblioteki), a następnie znacząca nazwa.
Uzgodnione - także z #define istnieje ogólne niebezpieczeństwo zmanipulowania kodu, ponieważ preprocesor nie zna składni.
NeilDurant
10
Lepiej jest użyć #if niż #ifdef, ale poza tym zgadzam się. +1.
Tim Post
58
To jest standardowa ewangelizacja w C ++. Poniższa odpowiedź jest DUŻO jaśniejsza w wyjaśnieniu, jakie opcje są naprawdę i co oznaczają. W szczególności: Właśnie miałem problem z „stałą statyczną”. Ktoś użył go do zdefiniowania około 2000 „stałych” w pliku nagłówkowym. Następnie ten plik nagłówkowy został zawarty w około 100 plikach „.c” i „.cpp”. => 8 MB dla „stałych”. Świetny. Tak, wiem, że możesz użyć linkera do usunięcia stałych, do których się nie odwołujesz, ale nadal pozostawia ci to, do których „stałych” się odwołujesz. Brakuje miejsca, co jest nie tak z tą odpowiedzią.
Ingo Blackman
2
@IngoBlackman: Przy dobrym kompilatorze staticpowinny pozostać tylko ci, których adres jest zajęty; a jeśli adres zostanie przyjęty, nie można byłoby użyć adresu #definelub enum(bez adresu) ... więc naprawdę nie widzę, jaką alternatywę można było zastosować. Jeśli możesz zrezygnować z „oceny czasu kompilacji”, możesz extern constzamiast tego szukać .
Matthieu M.,
15
@Tim postu: #ifmoże być korzystne ponad #ifdefdla logicznych flagami, ale w tym przypadku byłoby to uniemożliwiają, aby zdefiniować varjak 0z wiersza poleceń. Dlatego w tym przypadku #ifdefma to większy sens, o ile 0jest to zgodne z prawem var.
Maarten
108
W szczególności w C? W C poprawna odpowiedź to: użyj #define(lub, w razie potrzeby enum)
Chociaż korzystne jest posiadanie właściwości określania zakresu i pisania constobiektu, w rzeczywistości constobiekty w C (w przeciwieństwie do C ++) nie są prawdziwymi stałymi, a zatem są zazwyczaj bezużyteczne w większości praktycznych przypadków.
Tak więc w C wybór powinien zależeć od tego, jak planujesz użyć stałej. Na przykład nie można użyć const intobiektu jako caseetykiety (podczas gdy makro będzie działać). Nie możesz użyć const intobiektu jako szerokości pola bitowego (podczas gdy makro będzie działać). W C89 / 90 nie można użyć constobiektu do określenia rozmiaru tablicy (podczas gdy makro będzie działać). Nawet w C99 nie można użyć constobiektu do określenia rozmiaru tablicy, gdy potrzebna jest tablica inna niż VLA .
Jeśli jest to dla ciebie ważne, to określi twój wybór. Przez większość czasu nie będziesz miał wyboru, jak tylko użyć #definew C. I nie zapomnij o innej alternatywie, która daje prawdziwe stałe w C - enum.
W C ++ constobiekty są prawdziwymi stałymi, więc w C ++ prawie zawsze lepiej jest preferować constwariant ( staticchoć nie trzeba jawnie w C ++).
„nie można użyć obiektu const int jako etykiety sprawy (podczas gdy makro będzie działać)” ---> W odniesieniu do tego oświadczenia przetestowałem zmienną const int w C w przypadku przełącznika - skrzynka działa…
John
8
@ john: Musisz podać testowany kod i nazwać konkretny kompilator. Używanie const intobiektów w etykietach przypadków jest nielegalne we wszystkich wersjach języka C. (Oczywiście, twój kompilator może go obsługiwać jako niestandardowe rozszerzenie języka podobne do C ++).
AnT
11
„... i dlatego są zwykle bezużyteczne w większości praktycznych przypadków ”. Nie zgadzam się. Są doskonale przydatne, o ile nie musisz używać nazwy jako stałego wyrażenia. Słowo „stały” w C oznacza coś, co można ocenić w czasie kompilacji; constoznacza tylko do odczytu. const int r = rand();jest całkowicie legalny.
Keith Thompson
W c ++ lepiej jest używać constexprw porównaniu ze constspecjalnymi stlkontenerami, takimi jak arraylub bitset.
Mayukh Sarkar
1
@ John musisz przetestować w switch()oświadczeniu, a nie w casejednym. Właśnie mnie przyłapano ☺
Hi-Angel
32
Różnica między static consti #definepolega na tym, że pierwsza wykorzystuje pamięć, a druga nie używa pamięci do przechowywania. Po drugie, nie można przekazać adresu, #definenatomiast można przekazać adresstatic const . W rzeczywistości zależy to od okoliczności, w jakich się znajdujemy, musimy wybrać jedną z tych dwóch. Oba są najlepsze w różnych okolicznościach. Proszę nie zakładać, że jedno jest lepsze od drugiego ... :-)
Gdyby tak było, Dennis Ritchie zatrzymałby najlepszy w spokoju ... hahaha ... :-)
+1 za wzmiankę o pamięci, niektóre systemy wbudowane wciąż nie mają tak dużo, chociaż prawdopodobnie zacznę od używania stałych statycznych i w razie potrzeby zmienię na #defines.
fluffyben
3
Właśnie to przetestowałem. Rzeczywiście, const int używa dodatkowej pamięci w porównaniu do #define lub enum. Ponieważ programujemy systemy wbudowane, nie możemy sobie pozwolić na dodatkowe użycie pamięci. Wrócimy do używania #define lub enum.
Davide Andrea
2
Praktycznie rzecz biorąc, nie jest już prawdą, że a constkorzysta z pamięci. GCC (testowane z 4.5.3 i kilkoma nowszymi wersjami) łatwo optymalizuje const intbezpośredni literał w twoim kodzie, gdy używasz -O3. Więc jeśli wykonujesz programowanie z małą ilością pamięci RAM (np. AVR), możesz bezpiecznie używać const const, jeśli używasz GCC lub innego kompatybilnego kompilatora. Nie testowałem tego, ale oczekuję, że Clang zrobi to samo.
Raphael,
19
W C #definejest znacznie bardziej popularny. Tych wartości można użyć do deklarowania rozmiarów tablic, na przykład:
#define MAXLEN 5void foo(void){int bar[MAXLEN];}
static constO ile mi wiadomo, ANSI C nie pozwala ci używać s w tym kontekście. W C ++ w takich przypadkach należy unikać makr. Możesz pisać
constint maxlen =5;void foo(){int bar[maxlen];}
a nawet pomijać, staticponieważ wewnętrzne powiązanie jest sugerowane przez constjuż [tylko w C ++].
Co masz na myśli przez „wewnętrzne powiązanie”? Mogę mieć const int MY_CONSTANT = 5;w jednym pliku i uzyskać do niego dostęp extern const int MY_CONSTANT;w innym. Nie mogłem znaleźć żadnych informacji w standardzie (przynajmniej C99) na temat constzmiany domyślnego zachowania „6.2.2: 5 Jeśli deklaracja identyfikatora obiektu ma zakres pliku i nie ma specyfikatora klasy pamięci, jego powiązanie jest zewnętrzne”.
Gauthier
@Gauthier: Przepraszam, o tym. Powinienem był powiedzieć, że „implikuje const już w języku C ++”. Jest to specyficzne dla C ++.
sellibitze
@sellibitze miło jest zobaczyć po drodze kilka argumentów zamiast ton OPINII. Jeśli byłby bonus za prawdziwe argumenty, masz to!
Paul
1
Od C99 Twój drugi fragment jest legalny. barjest VLA (tablica o zmiennej długości); kompilator prawdopodobnie wygeneruje kod tak, jakby jego długość była stała.
Keith Thompson
14
Kolejną wadą constC jest to, że nie można użyć tej wartości do zainicjowania innej const.
staticintconst NUMBER_OF_FINGERS_PER_HAND =5;staticintconst NUMBER_OF_HANDS =2;// initializer element is not constant, this does not work.staticintconst NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND
* NUMBER_OF_HANDS;
Nawet to nie działa z const, ponieważ kompilator nie widzi tego jako stałej:
staticuint8_tconst ARRAY_SIZE =16;staticint8_tconst lookup_table[ARRAY_SIZE]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};// ARRAY_SIZE not a constant!
constW takich przypadkach chętnie skorzystam z wpisanego tekstu, w przeciwnym razie ...
Trochę późno do gry, ale to pytanie pojawiło się w innym pytaniu. Ściganie, dlaczego static uint8_t const ARRAY_SIZE = 16;nagle przestajesz się kompilować, może być nieco trudne, szczególnie gdy #define ARRAY_SIZE 256jest zakopane dziesięć warstw głęboko w splątanej sieci nagłówków. Że nazwa wszystkich wielkich liter ARRAY_SIZEprosi o kłopoty. Zarezerwuj ALL_CAPS dla makr i nigdy nie definiuj makra, które nie jest w formie ALL_CAPS.
David Hammen
@David: solidna rada, której będę przestrzegać.
Gauthier
1
4 lata później zaoszczędziłeś mi dużo czasu zastanawiając się, dlaczego nie mogłem „zagnieździć” const. To może być ocenione więcej!
Plouff,
11
Jeśli uda ci się uciec, static constma wiele zalet. Przestrzega normalnych zasad zakresu, jest widoczny w debuggerze i ogólnie przestrzega reguł, których przestrzegają zmienne.
Jednak przynajmniej w oryginalnym standardzie C nie jest to właściwie stała. Jeśli używasz #define var 5, możesz pisać int foo[var];jako deklarację, ale nie możesz tego zrobić (z wyjątkiem rozszerzenia kompilatora "z static const int var = 5;. Nie dzieje się tak w C ++, gdzie static constwersji można używać wszędzie tam, gdzie jest to #definemożliwe, i uważam, że to jest tak samo w przypadku C99.
Nie należy jednak nigdy nazywać #definestałej małą nazwą. Zastąpi wszelkie możliwe użycie tej nazwy do końca jednostki tłumaczeniowej. Stałe makr powinny znajdować się w przestrzeni, która jest faktycznie ich własną przestrzenią nazw, która tradycyjnie składa się z wielkich liter, być może z prefiksem.
Niestety tak nie jest w przypadku C99. constw C99 nadal nie jest prawdziwą stałą. Możesz zadeklarować rozmiar tablicy za pomocą constC99, ale tylko dlatego, że C99 obsługuje tablice o zmiennej długości. Z tego powodu będzie działać tylko tam, gdzie dozwolone są VLA. Na przykład, nawet w C99, nadal nie można użyć constdo deklarowania rozmiaru tablicy elementu w struct.
AnT
Chociaż prawdą jest, że C99 na to nie pozwala, GCC (testowane z 4.5.3) doskonale pozwoli ci zainicjować tablice o const introzmiarze tak, jakby to była const C ++ lub makro. Niezależnie od tego, czy chcesz polegać na tym odchyleniu GCC od standardu, oczywiście jest to twój wybór, osobiście bym z nim poszedł, chyba że naprawdę możesz przewidzieć użycie innego kompilatora niż GCC lub Clang, ten ostatni ma tutaj tę samą funkcję (testowane z Clangiem 3.7).
Raphael,
7
ZAWSZE lepiej jest używać const zamiast #define. Jest tak, ponieważ const jest traktowany przez kompilator, a #define przez preprocesor. To tak, jakby sam #define nie był częścią kodu (z grubsza mówiąc).
Przykład:
#define PI 3.1416
Kompilator nigdy nie może zobaczyć symbolicznej nazwy PI; może zostać usunięty przez preprocesor, zanim kod źródłowy dotrze nawet do kompilatora. W rezultacie nazwa PI może nie zostać wprowadzona do tablicy symboli. Może to być mylące, jeśli podczas kompilacji wystąpi błąd związany z użyciem stałej, ponieważ komunikat o błędzie może odnosić się do 3.1416, a nie PI. Gdyby PI zdefiniowano w pliku nagłówkowym, którego nie napisałeś, nie miałbyś pojęcia, skąd pochodzi ten 3.1416.
Ten problem może również pojawić się w symbolicznym debuggerze, ponieważ ponownie nazwa, którą programujesz, może nie znajdować się w tabeli symboli.
Preprocesor go zastąpi, a kod się nie skompiluje. Z tego powodu tradycyjny styl kodowania sugeruje, że wszystkie stałe #defines używają wielkich liter, aby uniknąć konfliktu.
nie zawsze definiuje stałą wartość. Niektóre kompilatory (na przykład tcc 0.9.26 ) po prostu przydzielają pamięć identyfikowaną nazwą „const_value”. Za pomocą identyfikatora „const_value” nie można modyfikować tej pamięci. Ale nadal możesz zmodyfikować pamięć za pomocą innego identyfikatora:
constint const_value =5;int*mutable_value =(int*)&const_value;*mutable_value =3;
printf("%i", const_value);// The output may be 5 or 3, depending on the compiler.
Oznacza to definicję
#define CONST_VALUE 5
jest jedynym sposobem zdefiniowania stałej wartości, której nie można zmodyfikować w żaden sposób.
Modyfikowanie stałej wartości za pomocą wskaźnika jest niezdefiniowanym zachowaniem. Jeśli chcesz się tam udać, #definemożesz również zmodyfikować, edytując kod maszynowy.
ugoren
Częściowo masz rację. Przetestowałem kod w Visual Studio 2012 i drukuje się 5. Ale nie można modyfikować, #defineponieważ jest to makro preprocesora. Nie istnieje w programie binarnym. Jeśli ktoś chciał zmodyfikować wszystkie miejsca, w których CONST_VALUEbył używany, musiał to zrobić jeden po drugim.
user2229691,
3
@ugoren: Załóżmy #define CONST 5zatem if (CONST == 5) { do_this(); } else { do_that(); }, że piszesz , a kompilator eliminuje elsegałąź. Jak proponujesz edytować kod maszynowy, aby zmienić CONSTna 6?
Keith Thompson
@KeithThompson, nigdy nie powiedziałem, że można to zrobić łatwo i niezawodnie. Tylko #defineto nie jest kuloodporne.
ugoren
3
@ugoren: Chodzi mi o to, że „edycja kodu maszynowego” nie jest rozsądnym sposobem na powielenie efektu zmiany wartości a #define. Jedynym prawdziwym sposobem na to jest edycja kodu źródłowego i ponowna kompilacja.
Keith Thompson
4
Chociaż pytanie dotyczyło liczb całkowitych, warto zauważyć, że #define i wyliczenia są bezużyteczne, jeśli potrzebujesz stałej struktury lub łańcucha. Oba są zwykle przekazywane do funkcji jako wskaźniki. (W przypadku ciągów jest to wymagane; w przypadku struktur jest znacznie bardziej wydajne).
Jeśli chodzi o liczby całkowite, jeśli jesteś w środowisku osadzonym z bardzo ograniczoną pamięcią, być może będziesz musiał się martwić o miejsce przechowywania stałej i sposób kompilowania dostępu do niej. Kompilator może dodać dwie stałe w czasie wykonywania, ale dodać dwa #definy w czasie kompilacji. Stała #define może zostać przekształcona w jedną lub więcej instrukcji MOV [natychmiastowych], co oznacza, że stała jest skutecznie przechowywana w pamięci programu. Stała stała zostanie zapisana w sekcji .const w pamięci danych. W systemach z architekturą Harvarda mogą występować różnice w wydajności i zużyciu pamięci, chociaż prawdopodobnie byłyby niewielkie. Mogą mieć znaczenie w przypadku twardej optymalizacji wewnętrznych pętli.
Nie myśl, że jest odpowiedź na „co zawsze jest najlepsze”, ale, jak powiedział Matthieu
static const
jest bezpieczny dla typu. #defineJednak moim największym wkurzeniem jest to , że podczas debugowania w Visual Studio nie można oglądać zmiennej. Daje błąd, że nie można znaleźć symbolu.
„nie możesz oglądać zmiennej” Tak, to nie jest zmienna. Nie zmienia się, dlaczego musisz to oglądać? Możesz znaleźć wszędzie, gdzie jest używany, po prostu szukając etykiety. Dlaczego miałbyś (a nawet chciałbyś) oglądać #define?
Marshall Eubanks
3
Nawiasem mówiąc, alternatywą dla #define, która zapewnia właściwy zakres, ale zachowuje się jak „prawdziwa” stała, jest „wyliczenie”. Na przykład:
enum{number_ten =10;}
W wielu przypadkach przydatne jest definiowanie typów wyliczanych i tworzenie zmiennych tych typów; jeśli tak się stanie, debuggery mogą wyświetlać zmienne zgodnie z nazwą ich wyliczenia.
Jest to jednak jedno ważne zastrzeżenie: w C ++ typy wyliczone mają ograniczoną zgodność z liczbami całkowitymi. Na przykład domyślnie nie można na nich wykonywać arytmetyki. Uważam, że jest to dziwne zachowanie domyślne dla wyliczeń; chociaż byłoby miło mieć typ „ścisłego wyliczania”, biorąc pod uwagę chęć, aby C ++ był ogólnie kompatybilny z C, sądzę, że domyślne zachowanie typu „wyliczeniowego” powinno być wymienne z liczbami całkowitymi.
W języku C stałe wyliczenia są zawsze typu int, więc „hacku wyliczeniowego” nie można używać z innymi typami liczb całkowitych. (Wyliczenie typ jest zgodny z jakimś realizacji zdefiniowanej typu całkowitego, niekoniecznie int, ale w tym przypadku jest to typ anonimowy, więc to nie ma znaczenia.)
Keith Thompson
@KeithThompson: Odkąd napisałem powyższe, przeczytałem, że MISRA-C będzie miażdżyć, jeśli kompilator przypisze inny typ niż intzmienna o typie wyliczenia (które kompilatory są dozwolone) i ktoś spróbuje przypisać taką zmienną członek własnego wyliczenia. Chciałbym, żeby komitety normalizacyjne dodały przenośne sposoby deklarowania typów całkowitych o określonej semantyce. DOWOLNA platforma, niezależnie od charwielkości, powinna być w stanie np. Zadeklarować typ, który obejmie mod 65536, nawet jeśli kompilator musi dodać wiele AND R0,#0xFFFFinstrukcji równoważnych.
supercat
Możesz użyć uint16_t, choć oczywiście nie jest to typ wyliczenia. Byłoby miło pozwolić użytkownikowi określić typ liczb całkowitych używanych do reprezentacji danego typu wyliczenia, ale można osiągnąć prawie taki sam efekt za pomocą typedeffor uint16_ti serii #defines dla poszczególnych wartości.
Keith Thompson
1
@KeithThompson: Rozumiem, że z przyczyn historycznych utknęliśmy w fakcie, że niektóre platformy będą oceniać 2U < -1Ljako prawdziwe, a inne jako fałszywe, a teraz utknęliśmy w fakcie, że niektóre platformy będą wdrażać porównanie między uint32_ti int32_tjak podpisano a niektóre jako niepodpisane, ale to nie znaczy, że Komitet nie mógł zdefiniować zgodnego w górę następcy C, który obejmuje typy, których semantyka byłaby spójna na wszystkich kompilatorach.
supercat
1
Prosta różnica:
W czasie wstępnego przetwarzania stała jest zastępowana jej wartością. Dlatego nie można zastosować operatora dereferencji do definicji, ale można zastosować operator dereferencji do zmiennej.
Jak można się spodziewać, definiowanie jest szybsze niż stała statyczna.
Na przykład mając:
#define mymax 100
nie możesz zrobić printf("address of constant is %p",&mymax); .
Ale mając
constint mymax_var=100
możesz to zrobić printf("address of constant is %p",&mymax_var); .
Aby być bardziej zrozumiałym, definicja jest zastępowana przez jej wartość na etapie wstępnego przetwarzania, więc nie mamy żadnej zmiennej przechowywanej w programie. Mamy tylko kod z segmentu tekstowego programu, w którym użyto definicji.
Jednak dla stałej statycznej mamy zmienną, która jest gdzieś przydzielona. W przypadku gcc stałe const są przydzielane w segmencie tekstowym programu.
Powyżej chciałem powiedzieć o operatorze referencyjnym, więc zastąp dereferencję referencją.
Twoja odpowiedź jest bardzo błędna. Chodzi o C, twoja odpowiedź dotyczy C ++, który ma bardzo różną semantykę dla constkwalifikatora. C nie ma stałych symbolicznych innych niż stałe wyliczeniowe . A const intjest zmienną. Mylisz także język i konkretne implementacje. Nie ma wymagania, gdzie umieścić obiekt. I nie jest to nawet prawdą w przypadku gcc: zwykle umieszcza constkwalifikowane zmienne w .rodatasekcji. Ale to zależy od platformy docelowej. Masz na myśli adres operatora &.
zbyt uczciwy dla tej strony
0
Przyjrzeliśmy się wytworzonemu kodowi asemblera na MBF16X ... Oba warianty dają ten sam kod dla operacji arytmetycznych (na przykład ADD Natychmiast).
const intJest więc preferowany do sprawdzania typu, podczas gdy #definejest w starym stylu. Może jest to specyficzne dla kompilatora. Więc sprawdź wyprodukowany kod asemblera.
Nie jestem pewien, czy mam rację, ale moim zdaniem dzwonię #define wartości d jest znacznie szybsze niż wywołanie innej normalnie zadeklarowanej zmiennej (lub stałej wartości). Dzieje się tak, ponieważ gdy program jest uruchomiony i musi użyć normalnie zadeklarowanej zmiennej, musi przejść do dokładnego miejsca w pamięci, aby uzyskać tę zmienną.
Przeciwnie, gdy używa #definewartości d, program nie musi przeskakiwać do żadnej przydzielonej pamięci, po prostu przyjmuje wartość. Jeśli #define myValue 7i program wywołujący myValue, zachowuje się dokładnie tak samo, jak po prostu wywołuje 7.
Odpowiedzi:
To zależy od tego, dla czego potrzebujesz wartości. Ty (i wszyscy dotychczas) pominąłeś trzecią opcję:
static const int var = 5;
#define var 5
enum { var = 5 };
Ignorując problemy dotyczące wyboru nazwy, a następnie:
Tak więc w większości kontekstów wolą „wyliczanie” niż alternatywy. W przeciwnym razie pierwszy i ostatni punkt kuli będą prawdopodobnie czynnikami kontrolującymi - i musisz się bardziej zastanowić, jeśli chcesz zaspokoić oba naraz.
Gdybyś pytał o C ++, używałbyś opcji (1) - stałej statycznej - za każdym razem.
źródło
enum
jest to, że są one zaimplementowane jakoint
([C99] 6.7.2.2/3). A#define
pozwala określić niepodpisane i długie zU
orazL
sufiksy orazconst
pozwala podać typ.enum
może powodować problemy z konwersjami zwykłego typu.enum
nie#define
wykorzystuje dodatkowej przestrzeni per se. Wartość pojawi się w kodzie obiektowym jako część instrukcji, a nie zostanie przydzielona pamięć w segmencie danych, w stercie lub na stosie. Będziesz miał trochę miejsca przydzielonego nastatic const int
, ale kompilator może go zoptymalizować, jeśli nie weźmiesz adresu.enum
s (istatic const
): nie można ich zmienić. adefine
może być#undefine
d, gdzieenum
istatic const
są ustalone na podaną wartość.Ogólnie rzecz biorąc:
Ponieważ szanuje zakres i jest bezpieczny dla typu.
Jedyne zastrzeżenie, jakie widziałem: jeśli chcesz, aby zmienna była możliwie zdefiniowana w wierszu poleceń. Nadal istnieje alternatywa:
O ile to możliwe, zamiast makr / elipsy, używaj bezpiecznej dla danego typu alternatywy.
Jeśli naprawdę POTRZEBUJESZ użyć makra (na przykład chcesz
__FILE__
lub__LINE__
), to lepiej nazwij makro BARDZO ostrożnie: w swojej konwencji nazewnictwa Boost zaleca wszystkie wielkie litery, zaczynając od nazwy projektu (tutaj BOOST_ ), przeglądając bibliotekę, zauważysz, że po niej (na ogół) następuje nazwa określonego obszaru (biblioteki), a następnie znacząca nazwa.Zasadniczo sprawia, że długie nazwy :)
źródło
static
powinny pozostać tylko ci, których adres jest zajęty; a jeśli adres zostanie przyjęty, nie można byłoby użyć adresu#define
lubenum
(bez adresu) ... więc naprawdę nie widzę, jaką alternatywę można było zastosować. Jeśli możesz zrezygnować z „oceny czasu kompilacji”, możeszextern const
zamiast tego szukać .#if
może być korzystne ponad#ifdef
dla logicznych flagami, ale w tym przypadku byłoby to uniemożliwiają, aby zdefiniowaćvar
jak0
z wiersza poleceń. Dlatego w tym przypadku#ifdef
ma to większy sens, o ile0
jest to zgodne z prawemvar
.W szczególności w C? W C poprawna odpowiedź to: użyj
#define
(lub, w razie potrzebyenum
)Chociaż korzystne jest posiadanie właściwości określania zakresu i pisania
const
obiektu, w rzeczywistościconst
obiekty w C (w przeciwieństwie do C ++) nie są prawdziwymi stałymi, a zatem są zazwyczaj bezużyteczne w większości praktycznych przypadków.Tak więc w C wybór powinien zależeć od tego, jak planujesz użyć stałej. Na przykład nie można użyć
const int
obiektu jakocase
etykiety (podczas gdy makro będzie działać). Nie możesz użyćconst int
obiektu jako szerokości pola bitowego (podczas gdy makro będzie działać). W C89 / 90 nie można użyćconst
obiektu do określenia rozmiaru tablicy (podczas gdy makro będzie działać). Nawet w C99 nie można użyćconst
obiektu do określenia rozmiaru tablicy, gdy potrzebna jest tablica inna niż VLA .Jeśli jest to dla ciebie ważne, to określi twój wybór. Przez większość czasu nie będziesz miał wyboru, jak tylko użyć
#define
w C. I nie zapomnij o innej alternatywie, która daje prawdziwe stałe w C -enum
.W C ++
const
obiekty są prawdziwymi stałymi, więc w C ++ prawie zawsze lepiej jest preferowaćconst
wariant (static
choć nie trzeba jawnie w C ++).źródło
const int
obiektów w etykietach przypadków jest nielegalne we wszystkich wersjach języka C. (Oczywiście, twój kompilator może go obsługiwać jako niestandardowe rozszerzenie języka podobne do C ++).const
oznacza tylko do odczytu.const int r = rand();
jest całkowicie legalny.constexpr
w porównaniu zeconst
specjalnymistl
kontenerami, takimi jakarray
lubbitset
.switch()
oświadczeniu, a nie wcase
jednym. Właśnie mnie przyłapano ☺Różnica między
static const
i#define
polega na tym, że pierwsza wykorzystuje pamięć, a druga nie używa pamięci do przechowywania. Po drugie, nie można przekazać adresu,#define
natomiast można przekazać adresstatic const
. W rzeczywistości zależy to od okoliczności, w jakich się znajdujemy, musimy wybrać jedną z tych dwóch. Oba są najlepsze w różnych okolicznościach. Proszę nie zakładać, że jedno jest lepsze od drugiego ... :-)Gdyby tak było, Dennis Ritchie zatrzymałby najlepszy w spokoju ... hahaha ... :-)
źródło
const
korzysta z pamięci. GCC (testowane z 4.5.3 i kilkoma nowszymi wersjami) łatwo optymalizujeconst int
bezpośredni literał w twoim kodzie, gdy używasz -O3. Więc jeśli wykonujesz programowanie z małą ilością pamięci RAM (np. AVR), możesz bezpiecznie używać const const, jeśli używasz GCC lub innego kompatybilnego kompilatora. Nie testowałem tego, ale oczekuję, że Clang zrobi to samo.W C
#define
jest znacznie bardziej popularny. Tych wartości można użyć do deklarowania rozmiarów tablic, na przykład:static const
O ile mi wiadomo, ANSI C nie pozwala ci używać s w tym kontekście. W C ++ w takich przypadkach należy unikać makr. Możesz pisaća nawet pomijać,
static
ponieważ wewnętrzne powiązanie jest sugerowane przezconst
już [tylko w C ++].źródło
const int MY_CONSTANT = 5;
w jednym pliku i uzyskać do niego dostępextern const int MY_CONSTANT;
w innym. Nie mogłem znaleźć żadnych informacji w standardzie (przynajmniej C99) na tematconst
zmiany domyślnego zachowania „6.2.2: 5 Jeśli deklaracja identyfikatora obiektu ma zakres pliku i nie ma specyfikatora klasy pamięci, jego powiązanie jest zewnętrzne”.bar
jest VLA (tablica o zmiennej długości); kompilator prawdopodobnie wygeneruje kod tak, jakby jego długość była stała.Kolejną wadą
const
C jest to, że nie można użyć tej wartości do zainicjowania innejconst
.Nawet to nie działa z const, ponieważ kompilator nie widzi tego jako stałej:
const
W takich przypadkach chętnie skorzystam z wpisanego tekstu, w przeciwnym razie ...źródło
static uint8_t const ARRAY_SIZE = 16;
nagle przestajesz się kompilować, może być nieco trudne, szczególnie gdy#define ARRAY_SIZE 256
jest zakopane dziesięć warstw głęboko w splątanej sieci nagłówków. Że nazwa wszystkich wielkich literARRAY_SIZE
prosi o kłopoty. Zarezerwuj ALL_CAPS dla makr i nigdy nie definiuj makra, które nie jest w formie ALL_CAPS.const
. To może być ocenione więcej!Jeśli uda ci się uciec,
static const
ma wiele zalet. Przestrzega normalnych zasad zakresu, jest widoczny w debuggerze i ogólnie przestrzega reguł, których przestrzegają zmienne.Jednak przynajmniej w oryginalnym standardzie C nie jest to właściwie stała. Jeśli używasz
#define var 5
, możesz pisaćint foo[var];
jako deklarację, ale nie możesz tego zrobić (z wyjątkiem rozszerzenia kompilatora "zstatic const int var = 5;
. Nie dzieje się tak w C ++, gdziestatic const
wersji można używać wszędzie tam, gdzie jest to#define
możliwe, i uważam, że to jest tak samo w przypadku C99.Nie należy jednak nigdy nazywać
#define
stałej małą nazwą. Zastąpi wszelkie możliwe użycie tej nazwy do końca jednostki tłumaczeniowej. Stałe makr powinny znajdować się w przestrzeni, która jest faktycznie ich własną przestrzenią nazw, która tradycyjnie składa się z wielkich liter, być może z prefiksem.źródło
const
w C99 nadal nie jest prawdziwą stałą. Możesz zadeklarować rozmiar tablicy za pomocąconst
C99, ale tylko dlatego, że C99 obsługuje tablice o zmiennej długości. Z tego powodu będzie działać tylko tam, gdzie dozwolone są VLA. Na przykład, nawet w C99, nadal nie można użyćconst
do deklarowania rozmiaru tablicy elementu wstruct
.const int
rozmiarze tak, jakby to była const C ++ lub makro. Niezależnie od tego, czy chcesz polegać na tym odchyleniu GCC od standardu, oczywiście jest to twój wybór, osobiście bym z nim poszedł, chyba że naprawdę możesz przewidzieć użycie innego kompilatora niż GCC lub Clang, ten ostatni ma tutaj tę samą funkcję (testowane z Clangiem 3.7).ZAWSZE lepiej jest używać const zamiast #define. Jest tak, ponieważ const jest traktowany przez kompilator, a #define przez preprocesor. To tak, jakby sam #define nie był częścią kodu (z grubsza mówiąc).
Przykład:
Kompilator nigdy nie może zobaczyć symbolicznej nazwy PI; może zostać usunięty przez preprocesor, zanim kod źródłowy dotrze nawet do kompilatora. W rezultacie nazwa PI może nie zostać wprowadzona do tablicy symboli. Może to być mylące, jeśli podczas kompilacji wystąpi błąd związany z użyciem stałej, ponieważ komunikat o błędzie może odnosić się do 3.1416, a nie PI. Gdyby PI zdefiniowano w pliku nagłówkowym, którego nie napisałeś, nie miałbyś pojęcia, skąd pochodzi ten 3.1416.
Ten problem może również pojawić się w symbolicznym debuggerze, ponieważ ponownie nazwa, którą programujesz, może nie znajdować się w tabeli symboli.
Rozwiązanie:
źródło
#define var 5
spowoduje problemy, jeśli masz takie rzeczymystruct.var
.Na przykład,
Preprocesor go zastąpi, a kod się nie skompiluje. Z tego powodu tradycyjny styl kodowania sugeruje, że wszystkie stałe
#define
s używają wielkich liter, aby uniknąć konfliktu.źródło
Napisałem program szybkiego testu, aby wykazać jedną różnicę:
To kompiluje się z tymi błędami i ostrzeżeniami:
Zauważ, że wyliczanie daje błąd, gdy definiuje daje ostrzeżenie.
źródło
Definicja
nie zawsze definiuje stałą wartość. Niektóre kompilatory (na przykład tcc 0.9.26 ) po prostu przydzielają pamięć identyfikowaną nazwą „const_value”. Za pomocą identyfikatora „const_value” nie można modyfikować tej pamięci. Ale nadal możesz zmodyfikować pamięć za pomocą innego identyfikatora:
Oznacza to definicję
jest jedynym sposobem zdefiniowania stałej wartości, której nie można zmodyfikować w żaden sposób.
źródło
#define
możesz również zmodyfikować, edytując kod maszynowy.5
. Ale nie można modyfikować,#define
ponieważ jest to makro preprocesora. Nie istnieje w programie binarnym. Jeśli ktoś chciał zmodyfikować wszystkie miejsca, w którychCONST_VALUE
był używany, musiał to zrobić jeden po drugim.#define CONST 5
zatemif (CONST == 5) { do_this(); } else { do_that(); }
, że piszesz , a kompilator eliminujeelse
gałąź. Jak proponujesz edytować kod maszynowy, aby zmienićCONST
na 6?#define
to nie jest kuloodporne.#define
. Jedynym prawdziwym sposobem na to jest edycja kodu źródłowego i ponowna kompilacja.Chociaż pytanie dotyczyło liczb całkowitych, warto zauważyć, że #define i wyliczenia są bezużyteczne, jeśli potrzebujesz stałej struktury lub łańcucha. Oba są zwykle przekazywane do funkcji jako wskaźniki. (W przypadku ciągów jest to wymagane; w przypadku struktur jest znacznie bardziej wydajne).
Jeśli chodzi o liczby całkowite, jeśli jesteś w środowisku osadzonym z bardzo ograniczoną pamięcią, być może będziesz musiał się martwić o miejsce przechowywania stałej i sposób kompilowania dostępu do niej. Kompilator może dodać dwie stałe w czasie wykonywania, ale dodać dwa #definy w czasie kompilacji. Stała #define może zostać przekształcona w jedną lub więcej instrukcji MOV [natychmiastowych], co oznacza, że stała jest skutecznie przechowywana w pamięci programu. Stała stała zostanie zapisana w sekcji .const w pamięci danych. W systemach z architekturą Harvarda mogą występować różnice w wydajności i zużyciu pamięci, chociaż prawdopodobnie byłyby niewielkie. Mogą mieć znaczenie w przypadku twardej optymalizacji wewnętrznych pętli.
źródło
Nie myśl, że jest odpowiedź na „co zawsze jest najlepsze”, ale, jak powiedział Matthieu
static const
jest bezpieczny dla typu.
#define
Jednak moim największym wkurzeniem jest to , że podczas debugowania w Visual Studio nie można oglądać zmiennej. Daje błąd, że nie można znaleźć symbolu.źródło
Nawiasem mówiąc, alternatywą dla
#define
, która zapewnia właściwy zakres, ale zachowuje się jak „prawdziwa” stała, jest „wyliczenie”. Na przykład:W wielu przypadkach przydatne jest definiowanie typów wyliczanych i tworzenie zmiennych tych typów; jeśli tak się stanie, debuggery mogą wyświetlać zmienne zgodnie z nazwą ich wyliczenia.
Jest to jednak jedno ważne zastrzeżenie: w C ++ typy wyliczone mają ograniczoną zgodność z liczbami całkowitymi. Na przykład domyślnie nie można na nich wykonywać arytmetyki. Uważam, że jest to dziwne zachowanie domyślne dla wyliczeń; chociaż byłoby miło mieć typ „ścisłego wyliczania”, biorąc pod uwagę chęć, aby C ++ był ogólnie kompatybilny z C, sądzę, że domyślne zachowanie typu „wyliczeniowego” powinno być wymienne z liczbami całkowitymi.
źródło
int
, więc „hacku wyliczeniowego” nie można używać z innymi typami liczb całkowitych. (Wyliczenie typ jest zgodny z jakimś realizacji zdefiniowanej typu całkowitego, niekoniecznieint
, ale w tym przypadku jest to typ anonimowy, więc to nie ma znaczenia.)int
zmienna o typie wyliczenia (które kompilatory są dozwolone) i ktoś spróbuje przypisać taką zmienną członek własnego wyliczenia. Chciałbym, żeby komitety normalizacyjne dodały przenośne sposoby deklarowania typów całkowitych o określonej semantyce. DOWOLNA platforma, niezależnie odchar
wielkości, powinna być w stanie np. Zadeklarować typ, który obejmie mod 65536, nawet jeśli kompilator musi dodać wieleAND R0,#0xFFFF
instrukcji równoważnych.uint16_t
, choć oczywiście nie jest to typ wyliczenia. Byłoby miło pozwolić użytkownikowi określić typ liczb całkowitych używanych do reprezentacji danego typu wyliczenia, ale można osiągnąć prawie taki sam efekt za pomocątypedef
foruint16_t
i serii#define
s dla poszczególnych wartości.2U < -1L
jako prawdziwe, a inne jako fałszywe, a teraz utknęliśmy w fakcie, że niektóre platformy będą wdrażać porównanie międzyuint32_t
iint32_t
jak podpisano a niektóre jako niepodpisane, ale to nie znaczy, że Komitet nie mógł zdefiniować zgodnego w górę następcy C, który obejmuje typy, których semantyka byłaby spójna na wszystkich kompilatorach.Prosta różnica:
W czasie wstępnego przetwarzania stała jest zastępowana jej wartością. Dlatego nie można zastosować operatora dereferencji do definicji, ale można zastosować operator dereferencji do zmiennej.
Jak można się spodziewać, definiowanie jest szybsze niż stała statyczna.
Na przykład mając:
nie możesz zrobić
printf("address of constant is %p",&mymax);
.Ale mając
możesz to zrobić
printf("address of constant is %p",&mymax_var);
.Aby być bardziej zrozumiałym, definicja jest zastępowana przez jej wartość na etapie wstępnego przetwarzania, więc nie mamy żadnej zmiennej przechowywanej w programie. Mamy tylko kod z segmentu tekstowego programu, w którym użyto definicji.
Jednak dla stałej statycznej mamy zmienną, która jest gdzieś przydzielona. W przypadku gcc stałe const są przydzielane w segmencie tekstowym programu.
Powyżej chciałem powiedzieć o operatorze referencyjnym, więc zastąp dereferencję referencją.
źródło
const
kwalifikatora. C nie ma stałych symbolicznych innych niż stałe wyliczeniowe . Aconst int
jest zmienną. Mylisz także język i konkretne implementacje. Nie ma wymagania, gdzie umieścić obiekt. I nie jest to nawet prawdą w przypadku gcc: zwykle umieszczaconst
kwalifikowane zmienne w.rodata
sekcji. Ale to zależy od platformy docelowej. Masz na myśli adres operatora&
.Przyjrzeliśmy się wytworzonemu kodowi asemblera na MBF16X ... Oba warianty dają ten sam kod dla operacji arytmetycznych (na przykład ADD Natychmiast).
const int
Jest więc preferowany do sprawdzania typu, podczas gdy#define
jest w starym stylu. Może jest to specyficzne dla kompilatora. Więc sprawdź wyprodukowany kod asemblera.źródło
Nie jestem pewien, czy mam rację, ale moim zdaniem dzwonię
#define
wartości d jest znacznie szybsze niż wywołanie innej normalnie zadeklarowanej zmiennej (lub stałej wartości). Dzieje się tak, ponieważ gdy program jest uruchomiony i musi użyć normalnie zadeklarowanej zmiennej, musi przejść do dokładnego miejsca w pamięci, aby uzyskać tę zmienną.Przeciwnie, gdy używa
#define
wartości d, program nie musi przeskakiwać do żadnej przydzielonej pamięci, po prostu przyjmuje wartość. Jeśli#define myValue 7
i program wywołującymyValue
, zachowuje się dokładnie tak samo, jak po prostu wywołuje7
.źródło