Apakah lebih baik menggunakan #define atau const int untuk konstanta?

26

Arduino adalah hibrida aneh, di mana beberapa fungsionalitas C ++ digunakan di dunia yang disematkan — biasanya lingkungan C. Memang, banyak kode Arduino sangat C seperti.

C secara tradisional digunakan #defines untuk konstanta. Ada sejumlah alasan untuk ini:

  1. Anda tidak dapat mengatur ukuran array menggunakan const int.
  2. Anda tidak dapat menggunakan const intsebagai label pernyataan kasus (meskipun ini berfungsi di beberapa kompiler)
  3. Anda tidak dapat menginisialisasi constdengan yang lain const.

Anda dapat memeriksa pertanyaan ini di StackOverflow untuk alasan lebih lanjut.

Jadi, apa yang harus kita gunakan untuk Arduino? Saya cenderung ke arah sana #define, tetapi saya melihat beberapa kode menggunakan constdan beberapa menggunakan campuran.

Cybergibbons
sumber
pengoptimal yang baik akan membuatnya moot
ratchet freak
3
Sangat? Saya tidak melihat bagaimana kompiler akan menyelesaikan hal-hal seperti keamanan jenis, tidak dapat menggunakan untuk menentukan panjang array dan sebagainya.
Cybergibbons
Saya setuju. Plus, jika Anda melihat jawaban saya di bawah ini, saya menunjukkan bahwa ada keadaan ketika Anda tidak benar-benar tahu jenis yang akan digunakan, jadi #defineadalah pilihan yang jelas. Contoh saya dalam penamaan pin Analog - seperti A5. Tidak ada tipe yang sesuai untuk itu yang dapat digunakan sebagai satu const-satunya pilihan adalah menggunakan a #definedan membiarkan kompiler menggantinya sebagai input teks sebelum menafsirkan artinya.
SDsolar

Jawaban:

21

Sangat penting untuk dicatat bahwa const inttidak tidak berperilaku identik di C dan C ++, sehingga sebenarnya beberapa keberatan terhadap hal itu yang telah disinggung dalam pertanyaan asli dan jawaban luas Peter Bloomfields ini tidak valid:

  • Dalam C ++, const intkonstanta mengkompilasi nilai waktu dan dapat digunakan untuk menetapkan batas array, sebagai label kasus, dll.
  • const intkonstanta tidak harus menempati penyimpanan apa pun. Kecuali Anda mengambil alamat mereka atau menyatakan mereka dari luar, mereka umumnya hanya akan memiliki waktu kompilasi.

Namun, untuk konstanta integer, mungkin lebih baik menggunakan (dinamai atau anonim) enum. Saya sering suka ini karena:

  • Ini kompatibel dengan C.
  • Ini hampir sama amannya const int(setiap bit sama amannya dengan di C ++ 11).
  • Ini memberikan cara alami pengelompokan konstanta terkait.
  • Anda bahkan dapat menggunakannya untuk sejumlah kontrol namespace.

Jadi dalam program C ++ idiomatik, tidak ada alasan apa pun untuk digunakan #defineuntuk mendefinisikan konstanta integer. Bahkan jika Anda ingin tetap kompatibel dengan C (karena persyaratan teknis, karena Anda menendang sekolah lama, atau karena orang yang bekerja dengan Anda lebih suka dengan cara itu), Anda masih dapat menggunakan enumdan harus melakukannya, daripada menggunakannya #define.

microtherion
sumber
2
Anda meningkatkan beberapa poin yang sangat baik (terutama tentang batas array - saya belum menyadari kompiler standar dengan Arduino IDE yang mendukung itu). Tidak sepenuhnya benar untuk mengatakan bahwa konstanta waktu kompilasi tidak menggunakan penyimpanan, karena nilainya masih harus terjadi dalam kode (yaitu memori program daripada SRAM) di mana pun ia digunakan. Itu artinya ini memengaruhi Flash yang tersedia untuk semua jenis yang membutuhkan lebih banyak ruang daripada pointer.
Peter Bloomfield
1
"jadi sebenarnya beberapa keberatan terhadapnya yang telah disinggung dalam pertanyaan asli" - mengapa mereka tidak valid dalam pertanyaan asli, karena dinyatakan ini adalah kendala C?
Cybergibbons
@Cybergibbons Arduino didasarkan pada C ++, jadi tidak jelas bagi saya mengapa hanya kendala C yang terkait (kecuali kode Anda karena beberapa alasan perlu kompatibel dengan C juga).
microtherion
3
@ PeterR.Bloomfield, poin saya tentang konstanta yang tidak memerlukan penyimpanan tambahan terbatas const int. Untuk jenis yang lebih kompleks, Anda benar bahwa penyimpanan mungkin dialokasikan, tetapi meskipun demikian, Anda tidak akan lebih buruk daripada dengan a #define.
microtherion
7

EDIT: microtherion memberikan jawaban luar biasa yang mengoreksi beberapa poin saya di sini, khususnya tentang penggunaan memori.


Seperti yang telah Anda identifikasi, ada situasi tertentu di mana Anda dipaksa untuk menggunakan #define, karena kompiler tidak akan mengizinkan constvariabel. Demikian pula, dalam beberapa situasi Anda dipaksa untuk menggunakan variabel, seperti ketika Anda membutuhkan array nilai (yaitu Anda tidak dapat memiliki array #define).

Namun, ada banyak situasi lain di mana tidak selalu ada satu jawaban 'benar'. Berikut adalah beberapa pedoman yang akan saya ikuti:

Jenis keamanan
Dari sudut pandang pemrograman umum, constvariabel biasanya lebih disukai (jika memungkinkan). Alasan utama untuk itu adalah tipe-safety.

A #define(preprocessor macro) secara langsung menyalin nilai literal ke setiap lokasi dalam kode, membuat setiap penggunaan independen. Ini secara hipotetis dapat mengakibatkan ambiguitas, karena jenisnya mungkin akan diselesaikan secara berbeda tergantung pada bagaimana / di mana ia digunakan.

Sebuah constvariabel hanya pernah satu jenis, yang ditentukan oleh deklarasi, dan diselesaikan selama inisialisasi. Ini akan sering membutuhkan pemeran eksplisit sebelum berperilaku berbeda (meskipun ada berbagai situasi di mana ia dapat secara aman dipromosikan secara tipe). Paling tidak, kompiler dapat (jika dikonfigurasi dengan benar) memancarkan peringatan yang lebih dapat diandalkan ketika masalah jenis terjadi.

Solusi yang mungkin untuk ini adalah dengan memasukkan gips eksplisit atau tipe-suffix dalam a #define. Sebagai contoh:

#define THE_ANSWER (int8_t)42
#define NOT_QUITE_PI 3.14f

Pendekatan itu berpotensi menyebabkan masalah sintaksis dalam beberapa kasus, tergantung pada bagaimana itu digunakan.

Penggunaan memori
Tidak seperti komputasi untuk keperluan umum, memori jelas sangat mahal ketika berhadapan dengan sesuatu seperti Arduino. Menggunakan constvariabel vs. a #definedapat memengaruhi tempat data disimpan dalam memori, yang mungkin memaksa Anda untuk menggunakan satu atau lainnya.

  • const variabel akan (biasanya) disimpan dalam SRAM, bersama dengan semua variabel lainnya.
  • Nilai literal yang digunakan #defineakan sering disimpan dalam ruang program (memori Flash), di samping sketsa itu sendiri.

(Perhatikan bahwa ada berbagai hal yang dapat memengaruhi dengan tepat bagaimana dan di mana sesuatu disimpan, seperti konfigurasi dan optimisasi kompiler.)

SRAM dan Flash memiliki batasan berbeda (misalnya masing-masing 2 KB dan 32 KB untuk Uno). Untuk beberapa aplikasi, kehabisan SRAM sangat mudah, sehingga dapat membantu untuk memindahkan beberapa hal ke Flash. Kebalikannya juga mungkin, meskipun mungkin kurang umum.

PROGMEM
Memungkinkan untuk mendapatkan manfaat dari keamanan tipe sambil menyimpan data dalam ruang program (Flash). Ini dilakukan dengan menggunakan PROGMEMkata kunci. Itu tidak bekerja untuk semua jenis, tetapi itu biasanya digunakan untuk array bilangan bulat atau string.

Bentuk umum yang diberikan dalam dokumentasi adalah sebagai berikut:

dataType variableName[] PROGMEM = {dataInt0, dataInt1, dataInt3...}; 

Tabel string sedikit lebih rumit, tetapi dokumentasi memiliki detail lengkap.

Peter Bloomfield
sumber
1

Untuk variabel dari tipe tertentu yang tidak diubah selama eksekusi, biasanya dapat digunakan.

Untuk nomor pin Digital yang terkandung dalam variabel, keduanya dapat berfungsi - seperti:

const int ledPin = 13;

Tetapi ada satu keadaan di mana saya selalu menggunakan #define

Ini untuk menentukan nomor pin analog, karena mereka alfanumerik.

Tentu, Anda bisa keras-kode nomor pin sebagai a2, a3, dll di seluruh program dan compiler akan tahu apa yang harus dilakukan dengan mereka. Kemudian jika Anda mengganti pin maka setiap penggunaan perlu diubah.

Selain itu, saya selalu ingin memiliki definisi pin saya di atas semua di satu tempat, jadi pertanyaannya menjadi jenis constpin yang sesuai untuk pin yang didefinisikan A5.

Dalam kasus itu saya selalu menggunakan #define

Contoh Pembagi Tegangan:

//
//  read12     Reads Voltage of 12V Battery
//
//        SDsolar      8/8/18
//
#define adcInput A5    // Voltage divider output comes in on Analog A5
float R1 = 120000.0;   // R1 for voltage divider input from external 0-15V
float R2 =  20000.0;   // R2 for voltage divider output to ADC
float vRef = 4.8;      // 9V on Vcc goes through the regulator
float vTmp, vIn;
int value;
.
.
void setup() {
.
// allow ADC to stabilize
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin);
.
void loop () {
.
.
  value=analogRead(adcPin);
  vTmp = value * ( vRef / 1024.0 );  
  vIn = vTmp / (R2/(R1+R2)); 
 .
 .

Semua variabel pengaturan berada tepat di atas dan tidak akan pernah ada perubahan nilai adcPinkecuali pada waktu kompilasi.

Jangan khawatir tentang jenis apa adcPinitu. Dan tidak ada RAM tambahan yang digunakan dalam biner untuk menyimpan konstanta.

Kompilator hanya mengganti setiap instance adcPindengan string A5sebelum kompilasi.


Ada utas Arduino Forum yang menarik yang membahas cara-cara lain untuk memutuskan:

#define vs const variabel (forum Arduino)

Petuah:

Substitusi kode:

#define FOREVER for( ; ; )

FOREVER
 {
 if (serial.available() > 0)
   ...
 }

Kode debug:

#ifdef DEBUG
 #define DEBUG_PRINT(x) Serial.println(x)
#else
 #define DEBUG_PRINT(x)
#endif

Mendefinisikan truedan falsesebagai Boolean untuk menghemat RAM

Instead of using `const bool true = 1;` and same for `false`

#define true (boolean)1
#define false (boolean)0

Banyak yang turun ke preferensi pribadi, namun jelas bahwa #defineitu lebih fleksibel.

SDsolar
sumber
Dalam keadaan yang sama, a consttidak akan menggunakan lebih banyak RAM daripada a #define. Dan untuk pin analog, saya akan mendefinisikannya sebagai const uint8_t, meskipun const inttidak ada bedanya.
Edgar Bonet
Anda menulis " a consttidak benar-benar menggunakan lebih banyak RAM [...] sampai benar-benar digunakan ". Anda melewatkan poin saya: sebagian besar waktu, a consttidak menggunakan RAM, bahkan ketika itu digunakan . Kemudian, " ini adalah kompiler multipass ". Yang paling penting, ini adalah kompiler yang mengoptimalkan . Kapan pun memungkinkan, konstanta dioptimalkan menjadi operan langsung .
Edgar Bonet