Tốt hơn là sử dụng #define hoặc const int cho hằng số?


26

Arduino là một phép lai kỳ lạ, trong đó một số chức năng C ++ được sử dụng trong thế giới nhúng, theo truyền thống là môi trường C. Thật vậy, rất nhiều mã Arduino rất giống như C.

C có truyền thống sử dụng #defines cho hằng số. Có một số lý do cho việc này:

  1. Bạn không thể đặt kích thước mảng bằng cách sử dụng const int.
  2. Bạn không thể sử dụng const intlàm nhãn câu lệnh tình huống (mặc dù điều này không hoạt động trong một số trình biên dịch)
  3. Bạn không thể khởi tạo cái constkhác const.

Bạn có thể kiểm tra câu hỏi này trên StackOverflow để biết thêm lý do.

Vậy, chúng ta nên sử dụng gì cho Arduino? Tôi có xu hướng #define, nhưng tôi thấy một số mã sử dụng constvà một số sử dụng hỗn hợp.


một trình tối ưu hóa tốt sẽ khiến nó chạy nhanh
ratchet freak

3
Có thật không? Tôi không thấy trình biên dịch sẽ giải quyết những thứ như an toàn kiểu như thế nào, không thể sử dụng để xác định độ dài mảng, v.v.
Cyberg Ribbon

Tôi đồng ý. Ngoài ra, nếu bạn nhìn vào câu trả lời của tôi dưới đây, tôi chứng minh rằng có những trường hợp khi bạn không thực sự biết nên sử dụng loại nào, thì đó #definelà sự lựa chọn rõ ràng. Ví dụ của tôi là trong việc đặt tên cho các chân Analog - như A5. Không có loại thích hợp cho nó có thể được sử dụng như một constlựa chọn duy nhất là sử dụng a #definevà để trình biên dịch thay thế nó làm kiểu nhập văn bản trước khi diễn giải nghĩa.
SDsolar

Câu trả lời:


21

Điều quan trọng cần lưu ý là rằng const intkhông không cư xử hệt trong C và C ++, vì vậy trên thực tế một số trong những sự phản đối chống lại nó đã được ám chỉ trong câu hỏi ban đầu và trong câu trả lời sâu rộng Peter Bloomfields của không hợp lệ:

  • Trong C ++, const inthằng số là các giá trị thời gian biên dịch và có thể được sử dụng để đặt giới hạn mảng, như nhãn trường hợp, v.v.
  • const inthằng số không nhất thiết chiếm bất kỳ lưu trữ. Trừ khi bạn lấy địa chỉ của họ hoặc khai báo chúng bên ngoài, họ thường sẽ chỉ tồn tại thời gian biên dịch.

Tuy nhiên, đối với các hằng số nguyên, thường có thể sử dụng một (được đặt tên hoặc ẩn danh) enum. Tôi thường thích điều này vì:

  • Nó tương thích ngược với C.
  • Nó gần như là loại an toàn như const int(mọi bit như loại an toàn trong C ++ 11).
  • Nó cung cấp một cách tự nhiên để nhóm các hằng số liên quan.
  • Bạn thậm chí có thể sử dụng chúng cho một số lượng điều khiển không gian tên.

Vì vậy, trong một chương trình C ++ thành ngữ, không có lý do gì để sử dụng #defineđể xác định một hằng số nguyên. Ngay cả khi bạn muốn duy trì khả năng tương thích C (vì yêu cầu kỹ thuật, vì bạn đang học trường cũ hoặc vì những người bạn làm việc thích nó theo cách đó), bạn vẫn có thể sử dụng enumvà nên làm như vậy, thay vì sử dụng #define.


2
Bạn nêu ra một số điểm tuyệt vời (đặc biệt là về giới hạn mảng - Tôi chưa nhận ra trình biên dịch chuẩn với Arduino IDE được hỗ trợ). Mặc dù không đúng khi nói rằng hằng số thời gian biên dịch không sử dụng bộ lưu trữ, bởi vì giá trị của nó vẫn phải xảy ra trong mã (tức là bộ nhớ chương trình chứ không phải SRAM) ở bất cứ nơi nào nó được sử dụng. Điều đó có nghĩa là nó tác động đến Flash có sẵn cho bất kỳ loại nào chiếm nhiều không gian hơn con trỏ.
Peter Bloomfield

1
"Vì vậy, trên thực tế, một số phản đối chống lại nó đã được ám chỉ trong câu hỏi ban đầu" - tại sao chúng không có giá trị trong câu hỏi ban đầu, vì nó được nêu đây là những ràng buộc của C?
Cyberg Ribbon

@Cyberg Ribbon Arduino dựa trên C ++, vì vậy tôi không rõ lý do tại sao các ràng buộc C chỉ thích hợp (trừ khi mã của bạn vì một số lý do cũng cần phải tương thích với C).
microtherion

3
@ PeterR.Bloomfield, quan điểm của tôi về hằng số không yêu cầu lưu trữ thêm đã bị giới hạn const int. Đối với các loại phức tạp hơn, bạn có quyền lưu trữ có thể được phân bổ, nhưng ngay cả như vậy, bạn không chắc sẽ tệ hơn so với a #define.
microtherion

7

EDIT: microtherion đưa ra một câu trả lời xuất sắc trong đó sửa chữa một số điểm của tôi ở đây, đặc biệt là về việc sử dụng bộ nhớ.


Như bạn đã xác định, có một số tình huống bạn buộc phải sử dụng #define, vì trình biên dịch sẽ không cho phép một constbiến. Tương tự, trong một số trường hợp, bạn buộc phải sử dụng các biến, chẳng hạn như khi bạn cần một mảng các giá trị (nghĩa là bạn không thể có một mảng #define).

Tuy nhiên, có nhiều tình huống khác không nhất thiết phải có một câu trả lời 'chính xác'. Dưới đây là một số hướng dẫn mà tôi sẽ làm theo:

Loại an toàn
Từ quan điểm lập trình chung, constcác biến thường được ưa thích hơn (nếu có thể). Lý do chính cho điều đó là an toàn loại.

Một #define(macro tiền xử lý) sao chép trực tiếp giá trị bằng chữ vào từng vị trí trong mã, làm cho mọi cách sử dụng trở nên độc lập. Điều này về mặt giả thuyết có thể dẫn đến sự mơ hồ, bởi vì loại cuối cùng có thể được giải quyết khác nhau tùy thuộc vào cách thức / nơi nó được sử dụng.

Một constbiến chỉ là một loại, được xác định bởi khai báo của nó và được giải quyết trong quá trình khởi tạo. Nó thường sẽ yêu cầu một dàn diễn viên rõ ràng trước khi nó hoạt động khác đi (mặc dù có nhiều tình huống khác nhau mà nó có thể được quảng bá kiểu một cách an toàn). Ít nhất, trình biên dịch có thể (nếu được cấu hình đúng) phát ra cảnh báo đáng tin cậy hơn khi xảy ra sự cố kiểu.

Một cách giải quyết có thể có cho việc này là bao gồm một dàn diễn viên rõ ràng hoặc một hậu tố kiểu trong a #define. Ví dụ:

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

Cách tiếp cận đó có khả năng có thể gây ra vấn đề cú pháp trong một số trường hợp, tùy thuộc vào cách sử dụng.

Sử dụng bộ nhớ
Không giống như tính toán cho mục đích chung, bộ nhớ rõ ràng là cao cấp khi xử lý một cái gì đó như Arduino. Sử dụng một constbiến so với a #definecó thể ảnh hưởng đến nơi dữ liệu được lưu trữ trong bộ nhớ, điều này có thể buộc bạn phải sử dụng cái này hoặc cái kia.

  • const các biến sẽ (thường) được lưu trữ trong SRAM, cùng với tất cả các biến khác.
  • Các giá trị bằng chữ được sử dụng #definethường sẽ được lưu trữ trong không gian chương trình (Bộ nhớ Flash), bên cạnh bản phác thảo.

(Lưu ý rằng có nhiều thứ khác nhau có thể ảnh hưởng đến chính xác cách thức và nơi lưu trữ thứ gì đó, chẳng hạn như cấu hình và tối ưu hóa trình biên dịch.)

SRAM và Flash có các giới hạn khác nhau (ví dụ: 2 KB và 32 KB tương ứng cho Uno). Đối với một số ứng dụng, việc sử dụng SRAM khá dễ dàng, vì vậy có thể hữu ích khi chuyển một số thứ sang Flash. Điều ngược lại cũng có thể, mặc dù có lẽ ít phổ biến hơn.

CHƯƠNG TRÌNH
Có thể nhận được các lợi ích của an toàn loại đồng thời lưu trữ dữ liệu trong không gian chương trình (Flash). Điều này được thực hiện bằng cách sử dụng PROGMEMtừ khóa. Nó không hoạt động cho tất cả các loại, nhưng nó thường được sử dụng cho các mảng số nguyên hoặc chuỗi.

Các hình thức chung được đưa ra trong tài liệu như sau:

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

Các bảng chuỗi phức tạp hơn một chút, nhưng tài liệu có đầy đủ chi tiết.


1

Đối với các biến của một loại xác định không bị thay đổi trong khi thực hiện, thường có thể được sử dụng.

Đối với số pin kỹ thuật số có trong các biến, có thể hoạt động - chẳng hạn như:

const int ledPin = 13;

Nhưng có một tình huống mà tôi luôn sử dụng #define

Đó là xác định số pin tương tự, vì chúng là chữ và số.

Chắc chắn, bạn có thể mã hóa cứng các con số pin như a2, a3vv tất cả trong suốt chương trình và trình biên dịch sẽ biết phải làm gì với họ. Sau đó, nếu bạn thay đổi chân thì mỗi lần sử dụng sẽ cần phải thay đổi.

Hơn nữa, tôi luôn muốn đặt các định nghĩa mã pin của mình ở trên cùng ở một nơi, vì vậy câu hỏi trở thành loại constnào sẽ phù hợp với mã pin được xác định là A5.

Trong những trường hợp đó tôi luôn sử dụng #define

Bộ chia điện áp Ví ​​dụ:

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

Tất cả các biến thiết lập đều ở ngay trên đầu và sẽ không bao giờ có sự thay đổi giá trị adcPinngoại trừ tại thời điểm biên dịch.

Không phải lo lắng về loại adcPinlà gì . Và không có thêm RAM được sử dụng trong nhị phân để lưu trữ một hằng số.

Trình biên dịch chỉ cần thay thế từng thể hiện của adcPinchuỗi A5trước khi biên dịch.


Có một chủ đề diễn đàn Arduino thú vị thảo luận về các cách khác để quyết định:

#define so với const const (diễn đàn Arduino)

Giới thiệu:

Thay thế mã:

#define FOREVER for( ; ; )

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

Mã gỡ lỗi:

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

Xác định truefalselàm Boolean để tiết kiệm RAM

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

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

Rất nhiều trong số đó thuộc về sở thích cá nhân, tuy nhiên rõ ràng #definelà linh hoạt hơn.


Trong cùng một trường hợp, a constsẽ không sử dụng nhiều RAM hơn a #define. Và đối với các chân tương tự, tôi sẽ định nghĩa chúng là const uint8_t, mặc dù const intsẽ không có sự khác biệt.
Edgar Bonet

Bạn đã viết ra một cách constkhông thực sự sử dụng nhiều RAM [...] cho đến khi nó thực sự được sử dụng . Bạn đã bỏ lỡ quan điểm của tôi: hầu hết thời gian, a constkhông sử dụng RAM, ngay cả khi nó được sử dụng . Sau đó, đây là một trình biên dịch nhiều tập . Quan trọng nhất, nó là một trình biên dịch tối ưu hóa . Bất cứ khi nào có thể, hằng số được tối ưu hóa thành toán hạng ngay lập tức .
Edgar Bonet
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.