Arduino to dziwna hybryda, w której niektóre funkcje C ++ są używane w świecie osadzonym - tradycyjnie środowisko C. Rzeczywiście, wiele kodu Arduino jest bardzo podobna do C.
C tradycyjnie używa #define
s dla stałych. Istnieje wiele powodów:
- Nie można ustawić rozmiarów tablic za pomocą
const int
. - Nie można używać
const int
jako etykiet instrukcji case (choć działa to w niektórych kompilatorach) - Nie możesz zainicjować
const
innegoconst
.
Możesz sprawdzić to pytanie na StackOverflow, aby uzyskać więcej uzasadnień.
Czego więc powinniśmy użyć dla Arduino? Skłaniam się w kierunku #define
, ale widzę trochę kodu używającego, const
a niektóre używającego mieszanki.
programming
c++
coding-standards
Cybergibbons
źródło
źródło
#define
jest to oczywisty wybór. Mój przykład dotyczy nazwania pinów analogowych - takich jak A5. Nie ma odpowiedniego typu, który mógłby być użyty jakoconst
tak, więc jedynym wyborem jest użycie a#define
i pozwolić kompilatorowi zastąpić go jako tekst wejściowy przed interpretacją znaczenia.Odpowiedzi:
Ważne jest, aby pamiętać, że
const int
nie nie zachowują się identycznie w C i C ++, więc w rzeczywistości kilka zarzutów przeciwko niemu, które zostały nawiązywał w pierwotnego pytania i odpowiedzi w obszernej Petera Bloomfields nie są ważne:const int
stałe są wartościami czasu kompilacji i mogą być używane do ustawiania limitów tablic, jak etykiety liter itp.const int
stałe niekoniecznie zajmują miejsce w pamięci. O ile nie weźmiesz ich adresu lub nie ogłosisz ich zewnętrznymi, na ogół będą one po prostu istnieć w czasie kompilacji.Jednak w przypadku stałych całkowitych często preferowane może być użycie (nazwanego lub anonimowego)
enum
. Często to lubię, ponieważ:const int
(co najmniej tak samo bezpieczny dla typu w C ++ 11).Tak więc w idiomatycznym programie C ++ nie ma żadnego powodu, aby używać go
#define
do definiowania stałej całkowitej. Nawet jeśli chcesz pozostać kompatybilny z C (z powodu wymagań technicznych, ponieważ kopiesz go w starej szkole lub ponieważ ludzie, z którymi pracujesz, wolą w ten sposób), możesz nadal używaćenum
i powinieneś to robić, zamiast używać#define
.źródło
const int
. W przypadku bardziej złożonych typów masz rację, że przestrzeń dyskowa może być przydzielona, ale mimo to raczej nie znajdziesz się w gorszej sytuacji niż w przypadku#define
.EDYCJA: mikrotherion daje doskonałą odpowiedź, która koryguje niektóre moje uwagi tutaj, szczególnie dotyczące zużycia pamięci.
Jak już zidentyfikowałeś, są pewne sytuacje, w których musisz użyć a
#define
, ponieważ kompilator nie zezwala naconst
zmienną. Podobnie, w niektórych sytuacjach jesteś zmuszony używać zmiennych, na przykład gdy potrzebujesz tablicy wartości (tzn. Nie możesz mieć tablicy#define
).Istnieje jednak wiele innych sytuacji, w których niekoniecznie istnieje jedna „poprawna” odpowiedź. Oto kilka wskazówek, których chciałbym przestrzegać:
Bezpieczeństwo typu
Z ogólnego punktu widzenia programowania
const
zmienne są zwykle preferowane (tam, gdzie to możliwe). Głównym tego powodem jest bezpieczeństwo typu.#define
(Preprocesor makro) bezpośrednio kopie wartości dosłownym do każdej lokalizacji w kodzie, dzięki czemu każdy niezależnie użytkowania. Może to hipotetycznie prowadzić do niejednoznaczności, ponieważ typ może zostać rozwiązany w różny sposób w zależności od tego, jak / gdzie jest używany.const
Zmienna jest zawsze tylko jeden typ, który jest określony przez jego deklaracji, a rozwiązany podczas inicjalizacji. Często będzie wymagało jawnego obsady, zanim będzie się zachowywać inaczej (chociaż istnieją różne sytuacje, w których można go bezpiecznie promować domyślnie). Kompilator może przynajmniej (jeśli poprawnie skonfigurowany) emitować bardziej niezawodne ostrzeżenie, gdy wystąpi problem z typem.Możliwym obejściem tego problemu jest dołączenie jawnej rzutowania lub sufiksu typu w pliku
#define
. Na przykład:Takie podejście może potencjalnie powodować problemy ze składnią w niektórych przypadkach, w zależności od tego, jak jest używane.
Zużycie pamięci
W przeciwieństwie do obliczeń ogólnego przeznaczenia, pamięć jest oczywiście cenna w przypadku czegoś takiego jak Arduino. Używanie
const
zmiennej vs.#define
może mieć wpływ na to, gdzie dane są przechowywane w pamięci, co może zmusić cię do użycia jednego lub drugiego.const
zmienne będą (zwykle) przechowywane w SRAM, wraz ze wszystkimi innymi zmiennymi.#define
będą często przechowywane w przestrzeni programu (pamięć Flash) obok samego szkicu.(Należy pamiętać, że istnieją różne rzeczy, które mogą wpływać dokładnie na to, jak i gdzie coś jest przechowywane, takie jak konfiguracja i optymalizacja kompilatora.)
SRAM i Flash mają różne ograniczenia (np. Odpowiednio 2 KB i 32 KB dla Uno). W przypadku niektórych aplikacji dość łatwo jest zabraknąć SRAM, więc może być pomocne przejście niektórych rzeczy na Flash. Odwrotna sytuacja jest również możliwa, choć prawdopodobnie mniej powszechna.
PROGMEM
Możliwe jest uzyskanie korzyści z bezpieczeństwa typu przy jednoczesnym przechowywaniu danych w przestrzeni programu (Flash). Odbywa się to za pomocą
PROGMEM
słowa kluczowego. Nie działa dla wszystkich typów, ale jest powszechnie używany do tablic liczb całkowitych lub ciągów.Ogólny formularz podany w dokumentacji jest następujący:
Tabele ciągów są nieco bardziej skomplikowane, ale dokumentacja zawiera pełne szczegóły.
źródło
W przypadku zmiennych określonego typu, które nie są zmieniane podczas wykonywania, zwykle można użyć albo.
W przypadku numerów pinów cyfrowych zawartych w zmiennych może działać dowolna - na przykład:
Ale jest jedna okoliczność, w której zawsze używam
#define
Służy do definiowania analogowych numerów pinów, ponieważ są one alfanumeryczne.
Jasne, można ciężko kod numery pin
a2
,a3
itp wszystkie w całym programie i kompilator będzie wiedział, co z nimi zrobić. Następnie, jeśli zmienisz piny, każde użycie będzie wymagało zmiany.Co więcej, zawsze lubię mieć moje definicje pinów na górze wszystko w jednym miejscu, więc
const
pojawia się pytanie, który typ byłby odpowiedni dla pin zdefiniowanego jakoA5
.W takich przypadkach zawsze używam
#define
Przykład dzielnika napięcia:
Wszystkie zmienne konfiguracji znajdują się na górze i nigdy nie będzie zmiany wartości, z
adcPin
wyjątkiem czasu kompilacji.Nie martw się o typ
adcPin
. W pamięci binarnej nie jest używana dodatkowa pamięć RAM do przechowywania stałej.Kompilator po prostu zastępuje każde wystąpienie
adcPin
łańcuchaA5
przed kompilacją.Istnieje interesujący wątek na forum Arduino, który omawia inne sposoby decydowania:
#define vs. const zmienna (forum Arduino)
Excertps:
Podstawienie kodu:
Kod debugowania:
Definiowanie
true
ifalse
jako wartość logiczna w celu oszczędzania pamięci RAMWiele z nich sprowadza się do osobistych preferencji, jednak jasne jest, że
#define
jest bardziej wszechstronny.źródło
const
nie zużywa więcej pamięci RAM niż a#define
. A dla pinów analogowych zdefiniowałbym je jakoconst uint8_t
, choćconst int
nie zrobiłoby to żadnej różnicy.const
tak naprawdę nie zużywa więcej pamięci RAM [...], dopóki nie zostanie faktycznie wykorzystane ”. Przegapiłeś mój punkt: przez większość czasu aconst
nie używa pamięci RAM, nawet jeśli jest używana . Następnie „ jest to kompilator wielopasmowy ”. Co najważniejsze, jest to kompilator optymalizujący . O ile to możliwe, stałe są optymalizowane do bezpośrednich operandów .