Po co używać zmiennej int dla pinu, gdy const int, enum lub #define ma o wiele większy sens


24

Dlaczego ludzie używają zmiennej, aby określić numer PINu, gdy jest mało prawdopodobne, że PIN zmieni się podczas wykonywania kodu?

Wiele razy widzę, intże używa się definicji pinów,

int led = 13;

kiedy użycie a const int

const int led = 13;

lub enumlub#define

#define LED 13

ma znacznie większy sens.

Jest nawet w tutorialach na stronie Arduino, na przykład w pierwszym tutorialu, który uruchamia większość ludzi, Blink .

Ja czytałem gdzieś , że const intjest korzystniejszy #define. Dlaczego nie zachęca się tego od samego początku, zamiast pozwolić ludziom od samego początku rozwijać złe nawyki? Zauważyłem to jakiś czas temu, ale ostatnio zaczęło mnie to irytować, stąd pytanie.

Pamięć / przetwarzanie / computing mądry jest const int, enumlub o to chodzi #define, lepiej niż zwykły int, czyli zajmuje mniej pamięci, przechowywane w innej pamięci (Flash, EEPROM, SRAM), szybsze wykonanie, szybciej skompilować?


Może się to wydawać duplikatem Czy lepiej używać #define lub const int dla stałych? , ale odpowiadam na pytanie, dlaczego ludzie używają zmiennych, i jak poprawia się wydajność, gdy tego nie robią, zamiast tego, który typ stałej jest lepszy.


9
Ponieważ okropne rodzą się okropne. Większość hobbystów nie jest doświadczonym programistą i dlatego uczy innych hobbystów złych nawyków.
Ignacio Vazquez-Abrams

1
Zwłaszcza w przypadku pinów, uproszczona forma podstawowych funkcji API arduino, takich jak digitalWrite, nie zachęca do właściwego osadzenia projektu, tj. Używania masek i pojedynczego adresu pamięci dla całego portu
krytyczne

Odpowiedzi:


20
const int led = 13;

To jest poprawna metoda. Lub nawet:

const byte led = 13;

Ile masz szpilek?

Niektóre samouczki nie poddały się tak dużej kontroli jakości, jak mogą.

Wydajność będzie lepsza w użyciu const byte, w porównaniu z inttym, że kompilator może być wystarczająco inteligentny, aby zrozumieć, co robisz.

To, co możesz zrobić, to delikatnie zachęcić ludzi do korzystania z bardziej wydajnych technik, używając ich we własnym kodzie.


Odpowiedzi na komentarze

  1. byteKomentator zasugerował, że nie jest to standard C. Jest to poprawne, jednak jest to strona Arduino StackExchange i uważam, że użycie standardowych typów dostarczanych przez Arduino IDE jest dopuszczalne.

    W Arduino.h jest ta linia:

    typedef uint8_t byte;

    Pamiętaj, że nie jest to dokładnie to samo co unsigned char. Zobacz uint8_t vs unsigned char i Kiedy uint8_t ≠ unsigned char? .

  2. Inny komentator zasugerował, że użycie bajtu niekoniecznie poprawi wydajność, ponieważ liczby mniejsze niż intbędą promowane int(patrz Zasady promocji liczb całkowitych, jeśli chcesz więcej na ten temat).

    Jednak w kontekście identyfikatora stałej kompilator generuje wydajny kod w każdym przypadku. Na przykład deasemblacja „mrugnięcia” daje to w oryginalnej formie:

    00000086 <loop>:
      86:   8d e0           ldi r24, 0x0D   ; 13
      88:   61 e0           ldi r22, 0x01   ; 1
      8a:   1b d1           rcall   .+566       ; 0x2c2 <digitalWrite>

    W rzeczywistości generuje ten sam kod, czy 13:

    • Jest dosłowne
    • Jest #define
    • Jest const int
    • Jest const byte

Kompilator wie, kiedy może zmieścić liczbę w jednym rejestrze, a kiedy nie. Jednak dobrą praktyką jest stosowanie kodowania, które wskazuje na twoje zamiary . Wyjaśnienie tego constjasno pokazuje, że liczba się nie zmieni, a wyjaśnienie byte(lub uint8_t) wyjaśnia, że ​​oczekujesz małej liczby.


Mylące komunikaty o błędach

Innym ważnym powodem, dla którego należy unikać, #definesą komunikaty o błędach wyświetlane w przypadku pomyłki. Rozważmy ten szkic „mrugnięcia”, który zawiera błąd:

#define LED = 13;

void setup() {
  pinMode(LED, OUTPUT);      // <---- line with error
}

void loop() {
  digitalWrite(LED, HIGH);   // <---- line with error 
  delay(1000);             
  digitalWrite(LED, LOW);    // <---- line with error
  delay(1000);              
}

Na powierzchni wygląda OK, ale generuje te komunikaty o błędach:

Blink.ino: In function ‘void setup()’:
Blink:4: error: expected primary-expression before ‘=’ token
Blink:4: error: expected primary-expression before ‘,’ token
Blink:4: error: expected `;' before ‘)’ token
Blink.ino: In function ‘void loop()’:
Blink:8: error: expected primary-expression before ‘=’ token
Blink:8: error: expected primary-expression before ‘,’ token
Blink:8: error: expected `;' before ‘)’ token
Blink:10: error: expected primary-expression before ‘=’ token
Blink:10: error: expected primary-expression before ‘,’ token
Blink:10: error: expected `;' before ‘)’ token

Patrzysz na pierwszą podświetloną linię (linia 4) i nawet nie widzisz symbolu „=”. Dodatkowo linia wygląda dobrze. Teraz dość oczywiste jest, na czym polega problem ( = 13jest zastępowany LED), ale gdy linia znajduje się 400 linii dalej w kodzie, nie jest oczywiste, że problem dotyczy sposobu definiowania diody LED.

Widziałem ludzi zakochanych wiele razy (w tym siebie).


Ile masz szpilek? to bardzo dobry punkt Nick, ponieważ większość desek ma tylko dziesiątki, a nie setki (tj. więcej niż 255), więc intjest przesada ... to znaczy, aż Arduino wreszcie wyjdzie z planszą Tera ... :-)
Greenonline

2
C nie ma bytetypu . Masz na myśli unsigned char.
Kevin

Wydajność niekoniecznie będzie lepsza byteniż int, ponieważ w większości kontekstów wartość całkowita z typami mniejszymi niż intsą promowane int.
Pete Becker

1
C doesn't have a byte type. You mean unsigned char.- Moja odpowiedź była w kontekście Arduino, który ma to typedef uint8_t byte;. Tak więc w przypadku Arduino używanie bytejest OK.
Nick Gammon

Performance won't necessarily be better with byte instead of int- patrz poprawiony post.
Nick Gammon

19

Jak słusznie stwierdza Ignacio, dzieje się tak głównie dlatego, że nie wiedzą lepiej. I nie wiedzą lepiej, ponieważ ludzie, którzy ich nauczali (lub zasoby, których używali podczas nauki) nie wiedzieli lepiej.

Znaczna część kodu i samouczków Arduino jest pisana przez ludzi, którzy nigdy nie mieli żadnego szkolenia w programowaniu i są bardzo „samoukami” z zasobów przez ludzi, którzy sami są bardzo samoukami bez odpowiedniego szkolenia w programowaniu.

Wiele fragmentów kodu samouczka, które widzę w tym miejscu (a zwłaszcza te, które są dostępne tylko w filmach na YouTube --- urgh) byłby znakiem nieudanym, gdybym zaznaczał je na egzaminie.

Tak, a constjest preferowane zamiast non-const, a nawet ponad a #define, ponieważ:

  • A const(jak a #define, w przeciwieństwie do non-const) nie przydziela pamięci RAM
  • A const(jak non-const, ale w przeciwieństwie do a #define) nadaje wartości jawny typ

Drugi punkt jest szczególnie interesujący. O ile nie powiedziano inaczej z osadzonym rzutowaniem typu ( (long)3) lub sufiksem typu ( 3L) lub obecnością kropki dziesiętnej ( 3.0), #definea liczby zawsze będzie liczbą całkowitą, a cała matematyka wykonana na tej wartości będzie tak, jakby była liczba całkowita. Przez większość czasu nie stanowi to problemu, ale możesz natknąć się na ciekawe scenariusze, gdy próbujesz #defineuzyskać wartość większą niż liczba całkowita, którą można zapisać, na przykład #define COUNT 70000wykonać następnie operację matematyczną z innymi intwartościami. Używając a, constmożesz powiedzieć kompilatorowi: „Tę wartość należy traktować jako ten typ zmiennej” - więc zamiast tego użyjesz: const long count = 70000;i wszystko będzie działać zgodnie z oczekiwaniami.

Ma również efekt domina, który sprawdza typ podczas przekazywania wartości wokół miejsca. Spróbuj przekazać a const longdo funkcji, która spodziewa się, inti narzekałaby na zawężenie zakresu zmiennych (lub nawet całkowicie nie skompilowała się w zależności od scenariusza). Zrób to za pomocą #definea, a po prostu w ciszy nadal będzie dawał złe wyniki i pozostawiasz drapanie się przez wiele godzin.


7
Warto zauważyć, że constzmienna może wymagać pamięci RAM, w zależności od kontekstu, np. Jeśli jest inicjowana przy użyciu wartości zwracanej z funkcji innej niż constexpr.
Peter Bloomfield,

Podobnie, na const int foo = 13; bar(&foo);pewno będzie wymagać od kompilatora alokacji faktycznej pamięci foo.
Ilmari Karonen,

3
Jeśli zdefiniujesz makro, które rozwija się do wartości, która nie będzie pasować intdo kompilatora, traktuje tę wartość jako najmniejszy typ, w którym się zmieści (modulo rządzi o podpisanym kontra niepodpisanym). Jeśli korzystasz z systemu, w którym intjest 16 bitów, #define count 70000spowoduje to , że będziesz countwyglądał jak a long, tak jakby był zdefiniowany jako const long count = 70000;. Ponadto, jeśli przekażesz jedną z tych wersji countdo oczekiwanej funkcji int, każdy rozsądny kompilator potraktuje je tak samo.
Pete Becker

1
Zgadzam się z @PeteBecker - konstrukcja #define COUNT 70000taka nie obcina się w liczbę całkowitą , ale kompilator traktuje ją jako typ wystarczająco duży, aby pomieścić tę liczbę. Prawdą jest, że może nie być oczywiste, kiedy używasz, COUNTże nie jest to int, ale i tak możesz powiedzieć to samo const long.
Nick Gammon

2
„#define zawsze będzie liczbą całkowitą” To nie jest prawda. Przyjmujesz reguły literałów całkowitych i stosujesz je do makr preprocesora. To jak porównywanie jabłek i muzyki pop. Wyrażenie COUNTw swoim przykładzie otrzymuje przed kompilacją z wypowiedzi 70000, która ma typ zdefiniowany przez przepisy literałów, podobnie jak 2lub 13Lczy 4.0są określone przez przepisy literałów. Fakt, że używasz #definedo aliasu tych wyrażeń, jest nieistotny. Jeśli chcesz, możesz użyć #definedo aliasu dowolnych fragmentów kodu C.
Wyścigi lekkości z Moniką

2

Jako 2-tygodniowy nowicjusz w Arduino podjąłem ogólną ideę zajmowania Arduino przez nie-programistów. Większość szkiców, które zbadałem, w tym te na stronie Arduino, wykazuje całkowity brak porządku, przy czym szkice nie działają i ledwo widzę spójny komentarz. Schematy blokowe nie istnieją, a „Biblioteki” to niemoderowana zbieranina.


0

Moja odpowiedź brzmi ... robią to, ponieważ to działa. Trudno mi nie zadać pytania w odpowiedzi, na przykład „dlaczego to musi być„ złe ”?”


3
Cechą charakterystyczną dobrego programisty jest to, że kod zawsze odzwierciedla ich intencje.
Ignacio Vazquez-Abrams,

1
Nadal rozmawiamy o Arduinos, prawda? ;)
linhartr22

3
Arduino ma już złą reputację w większej społeczności EE ze względu na przeciętne i okropne projekty sprzętu opracowane przez społeczność. Czy nie powinniśmy starać się o coś pierdolić ?
Ignacio Vazquez-Abrams,

2
„Większość projektów nie wiąże się z ryzykiem życia lub finansów ...” Nie ma w tym nic dziwnego. Kto by chciał zaangażować Arduino gdzie jest jakaś szansa ryzyka po patrząc na społeczności.
Ignacio Vazquez-Abrams,

2
To „źle” nie dlatego, że nie działa w jednej konkretnej sytuacji, ale dlatego, że w porównaniu do robienia tego „dobrze”, jest więcej sytuacji, w których to nie działa. To sprawia, że ​​kod jest kruchy; zmiany w kodzie mogą powodować tajemnicze awarie pochłaniające czas debugowania. Sprawdzanie typu i komunikaty o błędach kompilatora służą do wychwytywania tego rodzaju błędów wcześniej niż później.
Curt J. Sampson,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.