ควรใช้ #define หรือ const int สำหรับค่าคงที่หรือไม่


26

Arduino เป็นลูกผสมที่แปลกประหลาดซึ่งมีการใช้งาน C ++ บางอย่างในโลกที่ฝังตัวซึ่งปกติแล้วคือสภาพแวดล้อม C แท้จริงแล้วรหัส Arduino จำนวนมากนั้นเป็น C มาก

C มีการใช้แบบดั้งเดิม#defineสำหรับค่าคงที่ มีสาเหตุหลายประการ:

  1. คุณไม่สามารถตั้งค่าขนาดอาร์เรย์const intได้
  2. คุณไม่สามารถใช้const intเป็นป้ายกำกับคำสั่งกรณี (แม้ว่าจะใช้งานได้ในคอมไพเลอร์บางตัว)
  3. คุณไม่สามารถเริ่มต้นกับคนอื่นconstconst

คุณสามารถตรวจสอบคำถามนี้ใน StackOverflow เพื่อหาเหตุผลเพิ่มเติม

ดังนั้นสิ่งที่เราควรใช้สำหรับ Arduino? ฉันมีแนวโน้มที่จะไป#defineแต่ฉันเห็นบางรหัสใช้constและบางคนใช้ผสมผสาน


เครื่องมือเพิ่มประสิทธิภาพที่ดีจะทำให้ moot
ratchet freak

3
จริงๆ? ฉันไม่เห็นว่าคอมไพเลอร์จะแก้ไขสิ่งต่าง ๆ เช่นความปลอดภัยของประเภทไม่สามารถใช้เพื่อกำหนดความยาวของอาร์เรย์และอื่น ๆ
Cybergibbons

ฉันเห็นด้วย. นอกจากนี้หากคุณดูที่คำตอบของฉันด้านล่างฉันแสดงให้เห็นว่ามีสถานการณ์ที่คุณไม่ทราบว่าจะใช้ประเภทใดดังนั้นจึง#defineเป็นตัวเลือกที่ชัดเจน ตัวอย่างของฉันอยู่ในการตั้งชื่อหมุดแอนะล็อก - เช่น A5 ไม่มีประเภทที่เหมาะสมสำหรับมันที่สามารถใช้เป็นconstทางเลือกเดียวคือใช้#defineและให้คอมไพเลอร์แทนมันเป็นข้อความก่อนที่จะตีความความหมาย
SDsolar

คำตอบ:


21

มันเป็นสิ่งสำคัญที่จะทราบว่าconst intไม่ได้มีพฤติกรรมเหมือนใน C และ C ++ ดังนั้นในความเป็นจริงหลายของการคัดค้านต่อต้านมันที่ได้รับการพาดพิงถึงในคำถามเดิมและในคำตอบที่กว้างขวางปีเตอร์ Bloomfields ที่ไม่ถูกต้อง:

  • ใน C ++ const intค่าคงที่คือค่าเวลาการคอมไพล์และสามารถใช้เพื่อตั้งค่าขีด จำกัด ของอาเรย์เช่นเลเบลเคสและอื่น ๆ
  • const intค่าคงที่ไม่จำเป็นต้องใช้พื้นที่เก็บข้อมูลใด ๆ ถ้าคุณไม่ใช้ที่อยู่ของพวกเขาหรือประกาศพวกเขาออกไปข้างนอกพวกเขามักจะมีเวลารวบรวม

แต่สำหรับค่าคงที่จำนวนเต็มก็อาจมักจะดีกว่าที่จะใช้ enum(ชื่อหรือไม่ระบุชื่อ) ฉันมักจะชอบสิ่งนี้เพราะ:

  • มันย้อนกลับเข้ากันได้กับซี
  • มันเกือบจะเป็นประเภทที่ปลอดภัยเท่าconst int(ทุกบิตเป็นประเภทที่ปลอดภัยใน C ++ 11)
  • มันเป็นวิธีธรรมชาติของการจัดกลุ่มค่าคงที่เกี่ยวข้อง
  • คุณสามารถใช้เพื่อควบคุมเนมสเปซได้ด้วย

ดังนั้นในโปรแกรม C ++ ที่เป็นสำนวนไม่มีเหตุผลใด ๆ ที่จะใช้#defineเพื่อกำหนดค่าคงที่จำนวนเต็ม แม้ถ้าคุณต้องการที่จะอยู่ร่วมกันได้ C (เพราะข้อกำหนดทางเทคนิคเพราะคุณกำลัง Kickin' มันโรงเรียนเก่าหรือเพราะคนที่คุณต้องการทำงานกับมันเป็นอย่างนั้น) คุณยังสามารถใช้และควรทำมากกว่าการใช้งานenum#define


2
คุณยกระดับคะแนนที่ยอดเยี่ยมบางอย่าง (โดยเฉพาะเกี่ยวกับขีด จำกัด ของอาร์เรย์ - ฉันไม่ได้ตระหนักถึงคอมไพเลอร์มาตรฐานที่มี Arduino IDE สนับสนุนนั้น) มันค่อนข้างไม่ถูกต้องที่จะบอกว่าค่าคงที่เวลาคอมไพล์ใช้หน่วยความจำไม่ได้เนื่องจากค่าของมันยังต้องเกิดขึ้นในรหัส (เช่นหน่วยความจำของโปรแกรมมากกว่า SRAM) ทุกที่ที่ใช้ นั่นหมายความว่ามันส่งผลกระทบต่อแฟลชที่มีให้สำหรับประเภทใดก็ตามที่ใช้พื้นที่มากขึ้นกว่าตัวชี้
Peter Bloomfield

1
"ดังนั้นในความเป็นจริงหลายข้อคัดค้านต่อที่ได้รับการพาดพิงถึงในคำถามเดิม" - ทำไมพวกเขาไม่ถูกต้องในคำถามเดิมตามที่ระบุไว้เหล่านี้เป็นข้อ จำกัด ของ C?
Cybergibbons

@Cybergibbons Arduino ขึ้นอยู่กับ C ++ ดังนั้นจึงไม่ชัดเจนสำหรับฉันว่าทำไมข้อ จำกัด C เท่านั้นที่จะเกี่ยวข้อง (ยกเว้นว่ารหัสของคุณด้วยเหตุผลบางอย่างจำเป็นต้องเข้ากันได้กับ C เช่นกัน)
microtherion

3
@ PeterR.Bloomfield const intจุดของฉันเกี่ยวกับค่าคงที่ไม่ต้องจัดเก็บพิเศษถูกกักตัวไว้ สำหรับประเภทที่ซับซ้อนมากขึ้นคุณสิทธิกำลังที่จัดเก็บอาจได้รับการจัดสรร #defineแต่แม้ดังนั้นคุณไม่น่าจะเลวร้ายยิ่งกว่าด้วย
microtherion

7

แก้ไข: microtherion ให้คำตอบที่ดีซึ่งแก้ไขบางจุดของฉันที่นี่โดยเฉพาะเกี่ยวกับการใช้หน่วยความจำ


ตามที่คุณระบุมีบางสถานการณ์ที่คุณถูกบังคับให้ใช้#defineเพราะคอมไพเลอร์จะไม่อนุญาตให้ใช้constตัวแปร ในบางสถานการณ์คุณถูกบังคับให้ใช้ตัวแปรเช่นเมื่อคุณต้องการอาร์เรย์ของค่า (เช่นคุณไม่สามารถมีอาร์เรย์ได้#define)

อย่างไรก็ตามมีหลายสถานการณ์ที่ไม่จำเป็นต้องมีคำตอบ 'ถูกต้อง' เพียงคำเดียว นี่คือแนวทางบางส่วนที่ฉันจะทำตาม:

ความปลอดภัยของประเภท
จากมุมมองการเขียนโปรแกรมทั่วไปconstตัวแปรมักจะเป็นที่นิยม (หากเป็นไปได้) เหตุผลหลักสำหรับเรื่องนี้คือความปลอดภัยประเภท

A #define(มาโครตัวประมวลผลล่วงหน้า) คัดลอกค่าตัวอักษรไปยังแต่ละตำแหน่งในโค้ดโดยตรงทำให้การใช้งานทุกครั้งเป็นอิสระ สิ่งนี้อาจส่งผลให้เกิดความคลุมเครือในเชิงสมมุติฐานเนื่องจากรูปแบบอาจจบลงด้วยการแก้ไขแตกต่างกันไปขึ้นอยู่กับวิธี / ตำแหน่งที่ใช้

constตัวแปรเดียวที่เคยชนิดหนึ่งซึ่งจะถูกกำหนดโดยการประกาศของตนและได้รับการแก้ไขในช่วง initialisation มันมักจะต้องใช้การส่งที่ชัดเจนก่อนที่มันจะทำงานแตกต่างกัน (แม้ว่าจะมีสถานการณ์ต่าง ๆ ที่มันสามารถได้รับการส่งเสริมประเภทโดยนัย) อย่างน้อยที่สุดคอมไพเลอร์สามารถ (หากกำหนดค่าอย่างถูกต้อง) ส่งคำเตือนที่เชื่อถือได้มากขึ้นเมื่อเกิดปัญหาประเภท

#defineการแก้ปัญหาที่เป็นไปได้สำหรับเรื่องนี้คือการรวมทีมนักแสดงอย่างชัดเจนหรือชนิดคำต่อท้ายภายใน ตัวอย่างเช่น:

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

วิธีการดังกล่าวอาจทำให้เกิดปัญหาไวยากรณ์ในบางกรณีขึ้นอยู่กับวิธีการใช้งาน

การใช้หน่วยความจำ
แตกต่างจากการคำนวณด้วยคอมพิวเตอร์ทั่วไปหน่วยความจำมีความชัดเจนเมื่อใช้งานกับ Arduino การใช้constตัวแปร vs. #defineอาจส่งผลต่อการจัดเก็บข้อมูลในหน่วยความจำซึ่งอาจบังคับให้คุณใช้อย่างใดอย่างหนึ่ง

  • const ตัวแปรจะ (ปกติ) ถูกเก็บไว้ใน SRAM พร้อมกับตัวแปรอื่น ๆ ทั้งหมด
  • ค่าตัวอักษรที่ใช้ใน#defineมักจะถูกเก็บไว้ในพื้นที่โปรแกรม (หน่วยความจำแฟลช) พร้อมร่างภาพตัวเอง

(โปรดทราบว่ามีหลายสิ่งหลายอย่างที่สามารถส่งผลกระทบต่อวิธีการและสิ่งที่เก็บไว้เช่นการกำหนดค่าคอมไพเลอร์และการเพิ่มประสิทธิภาพ)

SRAM และ Flash มีข้อ จำกัด ที่แตกต่างกัน (เช่น 2 KB และ 32 KB ตามลำดับสำหรับ Uno) สำหรับแอพพลิเคชั่นบางอย่างมันค่อนข้างง่ายที่จะหมด SRAM ดังนั้นมันจะมีประโยชน์ในการเปลี่ยนบางสิ่งเป็น Flash การย้อนกลับยังเป็นไปได้แม้ว่าอาจพบได้น้อยกว่าทั่วไป

PROGMEM
เป็นไปได้ที่จะได้รับประโยชน์จากความปลอดภัยประเภทขณะเดียวกันก็เก็บข้อมูลไว้ในพื้นที่โปรแกรม (Flash) สิ่งนี้จะทำโดยใช้PROGMEMคำหลัก ใช้งานไม่ได้กับทุกประเภท แต่มักใช้กับอาร์เรย์ของจำนวนเต็มหรือสตริง

แบบฟอร์มทั่วไปที่ให้ไว้ในเอกสารมีดังนี้:

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

ตารางสตริงค่อนข้างซับซ้อนกว่าเล็กน้อย แต่เอกสารประกอบมีรายละเอียดครบถ้วน


1

สำหรับตัวแปรประเภทที่ระบุซึ่งไม่เปลี่ยนแปลงระหว่างการดำเนินการมักจะสามารถนำมาใช้

สำหรับหมายเลขพินดิจิตอลที่มีอยู่ในตัวแปรสามารถทำงานได้เช่น:

const int ledPin = 13;

แต่มีอยู่ครั้งหนึ่งที่ฉันมักจะใช้ #define

มันคือการกำหนดหมายเลขพินอะนาล็อกเนื่องจากเป็นตัวเลขและตัวอักษร

แน่นอนว่าคุณสามารถยากรหัสตัวเลขเข็มa2, a3ฯลฯ ตลอดทั้งโปรแกรมและคอมไพเลอร์จะรู้ว่าจะทำอย่างไรกับพวกเขา ถ้าคุณเปลี่ยนพินการใช้แต่ละครั้งจะต้องมีการเปลี่ยนแปลง

นอกจากนี้ผมมักจะชอบที่จะมีคำจำกัดความขาของฉันขึ้นที่ด้านบนทั้งหมดในที่เดียวเพื่อให้กลายเป็นคำถามประเภทจะเหมาะสมสำหรับขากำหนดให้เป็นconstA5

ในกรณีเหล่านั้นฉันมักจะใช้ #define

ตัวแบ่งแรงดันไฟฟ้าตัวอย่าง:

//
//  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)); 
 .
 .

ตัวแปรการตั้งค่าทั้งหมดอยู่ที่ด้านบนสุดและจะไม่มีการเปลี่ยนแปลงค่าadcPinยกเว้นในเวลารวบรวม

ไม่ต้องกังวลเกี่ยวกับประเภทadcPinใด และไม่มี RAM พิเศษถูกใช้ในไบนารีเพื่อเก็บค่าคงที่

คอมไพเลอร์เพียงแค่แทนที่แต่ละอินสแตนซ์ของadcPinกับสตริงA5ก่อนที่จะรวบรวม


มีเธรด Arduino Forum ที่น่าสนใจที่กล่าวถึงวิธีอื่น ๆ ในการตัดสินใจ:

#define vs. const variable (ฟอรัม Arduino)

Excertps:

การแทนที่รหัส:

#define FOREVER for( ; ; )

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

รหัสการแก้จุดบกพร่อง:

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

กำหนดtrueและfalseเป็นบูลีนเพื่อบันทึก RAM

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

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

มันมีหลายสิ่งที่เข้ากับความชอบส่วนบุคคล แต่เป็นที่ชัดเจนว่า#defineมีความหลากหลายมากขึ้น


ในสถานการณ์เดียวกันconstจะไม่ใช้ RAM #defineมากกว่าหนึ่ง และสำหรับหมุดอะนาล็อกฉันจะนิยามว่าเป็นconst uint8_tแม้ว่าconst intจะไม่ได้สร้างความแตกต่าง
Edgar Bonet

คุณเขียนว่า " a constไม่ได้ใช้ RAM มากกว่า [... ] จนกว่ามันจะใช้งานจริง " คุณพลาดจุดของฉัน: ส่วนใหญ่ของเวลาที่constไม่ได้ใช้ RAM, แม้เมื่อมันถูกนำมาใช้ จากนั้น“ นี่คือคอมไพเลอร์หลายรายการ ” ที่สำคัญที่สุดมันเป็นคอมไพเลอร์ที่ปรับให้เหมาะสม เมื่อใดก็ตามที่เป็นไปได้คงที่ได้รับการปรับให้เหมาะสมเข้าไปในตัวถูกดำเนินการทันที
Edgar Bonet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.