Którego lepiej użyć spośród poniższych instrukcji w C?
static const int var = 5;
lub
#define var 5
lub
enum { var = 5 };
Którego lepiej użyć spośród poniższych instrukcji w C?
static const int var = 5;
lub
#define var 5
lub
enum { var = 5 };
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.
enum
jest to, że są one zaimplementowane jako int
([C99] 6.7.2.2/3). A #define
pozwala określić niepodpisane i długie z U
oraz L
sufiksy oraz const
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 na static const int
, ale kompilator może go zoptymalizować, jeśli nie weźmiesz adresu.
enum
s (i static const
): nie można ich zmienić. a define
może być #undefine
d, gdzie enum
i static const
są ustalone na podaną wartość.
Ogólnie rzecz biorąc:
static const
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..
static int const var = VAR;
#else
static int const 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.
Zasadniczo sprawia, że długie nazwy :)
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
lub 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 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
jak 0
z wiersza poleceń. Dlatego w tym przypadku #ifdef
ma to większy sens, o ile 0
jest to zgodne z prawem var
.
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 const
obiektu, w rzeczywistości const
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 jako case
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 ++).
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 ze const
specjalnymi stl
kontenerami, takimi jak array
lub bitset
.
switch()
oświadczeniu, a nie w case
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 ... :-)
const
korzysta z pamięci. GCC (testowane z 4.5.3 i kilkoma nowszymi wersjami) łatwo optymalizuje const 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:
#define MAXLEN 5
void foo(void) {
int bar[MAXLEN];
}
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ć
const int maxlen = 5;
void foo() {
int bar[maxlen];
}
a nawet pomijać, static
ponieważ wewnętrzne powiązanie jest sugerowane przez const
już [tylko w C ++].
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 const
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 innej const
.
static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;
// initializer element is not constant, this does not work.
static int const 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:
static uint8_t const ARRAY_SIZE = 16;
static int8_t const 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!
const
W takich przypadkach chętnie skorzystam z wpisanego tekstu, w przeciwnym razie ...
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 liter ARRAY_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 "z static const int var = 5;
. Nie dzieje się tak w C ++, gdzie static 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.
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 w struct
.
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:
#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.
Rozwiązanie:
const double PI = 3.1416; //or static const...
#define var 5
spowoduje problemy, jeśli masz takie rzeczy mystruct.var
.
Na przykład,
struct mystruct {
int var;
};
#define var 5
int main() {
struct mystruct foo;
foo.var = 1;
return 0;
}
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.
Napisałem program szybkiego testu, aby wykazać jedną różnicę:
#include <stdio.h>
enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};
#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32
int main(int argc, char *argv[]) {
printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);
return(0);
}
To kompiluje się z tymi błędami i ostrzeżeniami:
main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
^
Zauważ, że wyliczanie daje błąd, gdy definiuje daje ostrzeżenie.
Definicja
const int const_value = 5;
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:
const int 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.
#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órych CONST_VALUE
był używany, musiał to zrobić jeden po drugim.
#define CONST 5
zatem if (CONST == 5) { do_this(); } else { do_that(); }
, że piszesz , a kompilator eliminuje else
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.
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.
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.
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.)
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 od char
wielkości, powinna być w stanie np. Zadeklarować typ, który obejmie mod 65536, nawet jeśli kompilator musi dodać wiele AND 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
for uint16_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ędzy uint32_t
i int32_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:
#define mymax 100
nie możesz zrobić printf("address of constant is %p",&mymax);
.
Ale mając
const int 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ą.
const
kwalifikatora. C nie ma stałych symbolicznych innych niż stałe wyliczeniowe . A const 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 umieszcza const
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.
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ący myValue
, zachowuje się dokładnie tak samo, jak po prostu wywołuje 7
.