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 5enum { 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.
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.
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.
enums (i static const): nie można ich zmienić. a definemoże być #undefined, gdzie enumi static constsą 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 :)
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ć .
#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.
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 ++).
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 ++).
constoznacza tylko do odczytu. const int r = rand();jest całkowicie legalny.
constexprw porównaniu ze constspecjalnymi stlkontenerami, takimi jak arraylub bitset.
switch()oświadczeniu, a nie w casejednym. Właśnie mnie przyłapano ☺
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 ... :-)
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.
W C #definejest 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 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ć
const int maxlen = 5;
void foo() {
int bar[maxlen];
}
a nawet pomijać, staticponieważ wewnętrzne powiązanie jest sugerowane przez constjuż [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 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”.
barjest VLA (tablica o zmiennej długości); kompilator prawdopodobnie wygeneruje kod tak, jakby jego długość była stała.
Kolejną wadą constC 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!
constW 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 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.
const. To może być ocenione więcej!
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.
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.
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).
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 #defines 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.
#definemożesz również zmodyfikować, edytując kod maszynowy.
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.
#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?
#defineto 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. #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.
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.)
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.
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.
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.
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ą.
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 &.
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.