„Static const” vs „#define” vs „enum”

585

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 };
Vijay
źródło
35
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.
MSalters
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ę:

  1. static const int var = 5;
  2. #define var 5
  3. 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.

Jonathan Leffler
źródło
111
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:

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 :)

Matthieu M.
źródło
2
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 ++).

Mrówka
źródło
6
„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 ... :-)

wrapperm
źródło
6
+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 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 ++].

sellibitze
źródło
1
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.

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 ...

Gauthier
źródło
5
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.

David Thornley
źródło
6
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.

Rozwiązanie:

const double PI = 3.1416; //or static const...
na pewno
źródło
6

#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.

Przerwanie niemaskowalne
źródło
6

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.

Michael Potter
źródło
4

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.

użytkownik2229691
źródło
8
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.

Adam Haun
źródło
3

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.

Afcrowe
źródło
1
„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.

supercat
źródło
1
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

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ą.

mihaitzateo
źródło
1
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.

Gość
źródło
-1

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.

pajczur
źródło