Là các biến toàn cầu xấu trong Arduino?


24

Tôi còn khá mới trong lập trình và nhiều cách thực hành mã hóa tốt nhất Tôi đang đọc một cách hiệu quả rằng có rất ít lý do chính đáng để sử dụng biến toàn cục (hoặc mã tốt nhất hoàn toàn không có toàn cầu).

Tôi đã cố gắng hết sức để ghi nhớ điều này, khi viết phần mềm để tạo giao diện Arduino bằng thẻ SD, nói chuyện với máy tính và chạy bộ điều khiển động cơ.

Tôi hiện có 46 toàn cầu cho khoảng 1100 dòng mã "cấp độ mới bắt đầu" (không có dòng nào có nhiều hơn một hành động). Đây có phải là một tỷ lệ tốt hay tôi nên xem xét giảm nó nhiều hơn? Ngoài ra những cách thực hành nào tôi có thể sử dụng để giảm số lượng toàn cầu hơn nữa?

Tôi đang hỏi điều này ở đây bởi vì tôi đặc biệt quan tâm đến các thực tiễn tốt nhất để mã hóa trên các sản phẩm Arduino hơn là lập trình máy tính nói chung.


2
Trong Arduino bạn không thể tránh các biến toàn cục. Mỗi khai báo biến ngoài phạm vi của một hàm / phương thức là toàn cục. Vì vậy, nếu bạn cần chia sẻ các giá trị giữa các hàm, chúng phải là toàn cục, trừ khi bạn muốn truyền mọi giá trị dưới dạng đối số.

16
@LookAlterno Err, bạn không thể viết các lớp trong Ardunio, vì nó chỉ là C ++ với các macro và thư viện kỳ ​​lạ? Nếu vậy, không phải mọi biến là toàn cầu. Và ngay cả trong C, nó thường được coi là cách thực hành tốt nhất để chuyển các biến (có lẽ bên trong cấu trúc) vào các hàm hơn là có các biến toàn cục. Có thể ít thuận tiện hơn cho một chương trình nhỏ nhưng nó thường được đền đáp khi chương trình trở nên lớn hơn và phức tạp hơn.
Muzer

11
@LookAlterno: "Tôi tránh""bạn không thể" là những thứ rất khác nhau.
Cuộc đua nhẹ nhàng với Monica

2
Trên thực tế, một số lập trình viên nhúng cấm các biến cục bộ và yêu cầu các biến toàn cục (hoặc các biến tĩnh có phạm vi hàm) thay vào đó. Khi các chương trình nhỏ, nó có thể giúp phân tích nó dễ dàng hơn như một máy trạng thái đơn giản; bởi vì các biến không bao giờ ghi đè lên nhau (nghĩa là: khi chúng thực hiện stack và heap biến được phân bổ).
Rob

1
Các biến tĩnh và toàn cầu cung cấp lợi thế của việc biết mức tiêu thụ bộ nhớ tại thời gian biên dịch. Với một arduino có bộ nhớ rất hạn chế có sẵn, đây có thể là một lợi thế. Nó khá dễ dàng cho một người mới làm cạn kiệt bộ nhớ có sẵn và trải nghiệm những thất bại không thể tìm thấy.
antipotype

Câu trả lời:


33

Chúng không xấu xa , nhưng chúng có xu hướng bị lạm dụng khi không có lý do chính đáng để sử dụng chúng.

Có rất nhiều lần khi các biến toàn cầu là lợi thế. Đặc biệt là từ khi lập trình một Arduino, dưới vỏ bọc, rất khác với lập trình PC.

Lợi ích lớn nhất cho các biến toàn cầu là phân bổ tĩnh. Đặc biệt với các biến lớn và phức tạp như các thể hiện của lớp. Phân bổ động (việc sử dụng newvv) được tán thành do thiếu tài nguyên.

Ngoài ra, bạn không nhận được một cây cuộc gọi như bạn làm trong một chương trình C bình thường ( main()hàm đơn gọi các hàm khác) - thay vào đó bạn có hai cây riêng biệt ( setup()gọi hàm, sau đó loop()gọi hàm), có nghĩa là đôi khi các biến toàn cục là cách duy nhất để đạt được mục tiêu của bạn (nghĩa là, nếu bạn muốn sử dụng nó trong cả hai setup()loop()).

Vì vậy, không, chúng không xấu, và trên Arduino, chúng có nhiều công dụng tốt hơn so với trên PC.


Ok, nếu đó là thứ gì đó tôi chỉ sử dụng trong loop()(hoặc trong nhiều chức năng được gọi là loop()) thì sao? sẽ tốt hơn để thiết lập chúng theo một cách khác hơn là xác định chúng lúc đầu?
ATE-ANHE

1
Trong trường hợp đó, tôi có thể định nghĩa chúng trong loop () (có thể như staticthể tôi cần chúng để giữ giá trị của chúng qua các lần lặp) và chuyển chúng qua các tham số chức năng trong chuỗi cuộc gọi.
Majenko

2
Đó là một cách, hoặc vượt qua nó như một tài liệu tham khảo: void foo(int &var) { var = 4; }foo(n);- nbây giờ là 4.
Majenko

1
Các lớp có thể được phân bổ ngăn xếp. Phải thừa nhận rằng, không tốt khi đặt các trường hợp lớn vào ngăn xếp, nhưng vẫn còn.
JAB

1
@JAB Bất cứ điều gì có thể được phân bổ ngăn xếp.
Majenko

18

Rất khó để đưa ra một câu trả lời dứt khoát mà không nhìn thấy mã thực tế của bạn.

Các biến toàn cục không phải là xấu và chúng thường có ý nghĩa trong một môi trường nhúng mà bạn thường truy cập nhiều vào phần cứng. Bạn chỉ có bốn UARTS, chỉ có một cổng I2C, v.v ... Vì vậy, thật hợp lý khi sử dụng toàn cầu cho các biến được gắn với tài nguyên phần cứng cụ thể. Và quả thực, thư viện lõi Arduino nào đó: Serial, Serial1vv là các biến toàn cầu. Ngoài ra, một biến đại diện cho trạng thái toàn cầu của chương trình thường là toàn cầu.

Tôi hiện có 46 toàn cầu cho khoảng 1100 dòng [mã]. Đây có phải là một tỷ lệ tốt [...]

Không phải là về những con số. Câu hỏi đúng bạn nên tự hỏi mình là, đối với mỗi một trong những thế giới này, liệu nó có ý nghĩa để có nó trong phạm vi toàn cầu.

Tuy nhiên, 46 toàn cầu có vẻ hơi cao đối với tôi. Nếu một số trong số này giữ các giá trị không đổi, hãy xác định chúng là const: trình biên dịch thường sẽ tối ưu hóa lưu trữ của chúng. Nếu bất kỳ biến nào trong số đó chỉ được sử dụng bên trong một hàm duy nhất, hãy biến nó thành cục bộ. Nếu bạn muốn giá trị của nó tồn tại giữa các lệnh gọi đến hàm, hãy xác nhận nó là static. Bạn cũng có thể giảm số lượng toàn cầu có thể nhìn thấy trên mạng bằng cách nhóm các biến lại với nhau trong một lớp và có một thể hiện toàn cầu của lớp này. Nhưng chỉ làm như vậy khi nó có ý nghĩa để đặt các công cụ với nhau. Tạo một GlobalStufflớp lớn với mục đích chỉ có một biến toàn cục sẽ không giúp mã của bạn rõ ràng hơn.


1
Được! Tôi không biết staticnếu tôi có một biến chỉ được sử dụng trong một hàm nhưng nhận được giá trị mới mỗi khi hàm được gọi (như var=millis()) tôi có nên thực hiện điều đó statickhông?
ATE-ANHE

3
Số staticchỉ dành cho khi bạn cần giữ giá trị. Nếu điều đầu tiên bạn làm trong hàm được đặt giá trị của biến thành thời gian hiện tại thì không cần phải giữ giá trị cũ. Tất cả những gì tĩnh làm trong tình huống đó là lãng phí bộ nhớ.
Andrew

Đây là một câu trả lời rất tròn trịa, thể hiện cả hai quan điểm ủng hộ và sự hoài nghi về toàn cầu. +1
gạch dưới

6

Vấn đề chính với các biến toàn cục là bảo trì mã. Khi đọc một dòng mã, rất dễ tìm thấy khai báo các biến được truyền dưới dạng tham số hoặc khai báo cục bộ. Không dễ để tìm thấy khai báo các biến toàn cục (thường là nó yêu cầu và IDE).

Khi bạn có nhiều biến toàn cục (40 đã là rất nhiều), sẽ rất khó để có một tên rõ ràng không quá dài. Sử dụng không gian tên là một cách để làm rõ vai trò của các biến toàn cục.

Một cách kém để bắt chước các không gian tên trong C là:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

Trên bộ xử lý intel hoặc arm, việc truy cập vào các biến toàn cục chậm hơn các biến khác. Nó có lẽ là ngược lại trên arduino.


2
Trên AVR, toàn cầu là trên RAM, trong khi hầu hết các địa phương không tĩnh được phân bổ cho các thanh ghi CPU, giúp truy cập của chúng nhanh hơn.
Edgar Bonet

1
Vấn đề không thực sự tìm thấy tuyên bố; việc có thể nhanh chóng hiểu được mã là quan trọng nhưng cuối cùng là thứ yếu so với những gì nó làm - và vấn đề thực sự là tìm ra phần nào trong phần mã không xác định của bạn có thể sử dụng khai báo toàn cầu để làm những điều kỳ lạ với một đối tượng bạn bỏ quên mở cho mọi người làm bất cứ điều gì họ muốn với.
gạch dưới

5

Mặc dù tôi sẽ không sử dụng chúng khi lập trình cho PC, nhưng đối với Arduino, chúng có một số lợi ích. Hầu hết nếu nó đã được nói:

  • Không sử dụng bộ nhớ động (tạo khoảng trống trong không gian heap giới hạn của Arduino)
  • Có sẵn ở mọi nơi, do đó không cần phải chuyển chúng dưới dạng đối số (chi phí không gian ngăn xếp)

Ngoài ra, trong một số trường hợp, đặc biệt là hiệu năng-khôn ngoan, có thể sử dụng các biến toàn cục, thay vì tạo các yếu tố khi cần:

  • Để giảm khoảng cách trong không gian heap
  • Phân bổ động và / hoặc giải phóng bộ nhớ có thể mất nhiều thời gian
  • Các biến có thể được "tái sử dụng", như một danh sách các thành phần của danh sách toàn cầu được sử dụng vì nhiều lý do, ví dụ như một lần làm bộ đệm cho SD và sau đó là bộ đệm chuỗi tạm thời.

5

Như với tất cả mọi thứ (trừ gotos thực sự xấu xa) toàn cầu có vị trí của họ.

ví dụ: Nếu bạn có một cổng nối tiếp gỡ lỗi hoặc tệp nhật ký mà bạn cần có thể ghi từ mọi nơi thì thường có ý nghĩa để làm cho nó trở nên toàn cầu. Tương tự như vậy nếu bạn có một số thông tin trạng thái hệ thống quan trọng thì làm cho nó toàn cầu thường là giải pháp dễ nhất. Không có điểm nào có một giá trị mà bạn phải truyền cho mọi chức năng duy nhất trong chương trình.

Như những người khác đã nói 46 dường như rất nhiều chỉ với hơn 1000 dòng mã nhưng không biết chi tiết về những gì bạn đang làm, thật khó để nói liệu bạn có sử dụng chúng quá mức hay không.

Tuy nhiên, đối với mọi người trên toàn cầu, hãy tự hỏi mình một vài câu hỏi quan trọng:

Tên có rõ ràng và cụ thể để tôi không vô tình thử sử dụng cùng tên ở một nơi khác không? Nếu không đổi tên.

Điều này có cần phải thay đổi? Nếu không thì xem xét làm cho nó một const.

Điều này có cần phải được nhìn thấy ở mọi nơi hay chỉ là toàn cầu để giá trị được duy trì giữa các lệnh gọi hàm? Vì vậy, hãy xem xét làm cho nó cục bộ cho chức năng và sử dụng từ khóa tĩnh.

Mọi thứ sẽ thực sự trở nên tồi tệ nếu điều này bị thay đổi bởi một đoạn mã khi tôi không cẩn thận? ví dụ: nếu bạn có hai biến liên quan, nói tên và số ID, là toàn cục (xem lưu ý trước về sử dụng toàn cầu khi bạn cần một số thông tin gần như ở mọi nơi) thì nếu một trong số chúng bị thay đổi mà không có những điều khó chịu khác có thể xảy ra. Vâng, bạn chỉ có thể cẩn thận nhưng đôi khi thật tốt khi thực thi cẩn thận một chút. ví dụ: đặt chúng vào một tệp .c khác và sau đó xác định các hàm đặt cả hai cùng một lúc và cho phép bạn đọc chúng. Sau đó, bạn chỉ bao gồm các hàm trong tệp tiêu đề được liên kết, theo cách đó, phần còn lại của mã của bạn chỉ có thể truy cập các biến thông qua các hàm được xác định và do đó không thể làm mọi thứ rối tung lên.

- cập nhật - Tôi mới nhận ra rằng bạn đã hỏi về thực tiễn tốt nhất về Arduino cụ thể hơn là mã hóa chung và đây là một câu trả lời mã hóa chung. Nhưng thành thật mà nói không có nhiều khác biệt, thực hành tốt là thực hành tốt. Các startup()loop()cấu trúc của phương tiện Arduino mà bạn phải sử dụng Globals hơn một chút so với các nền tảng khác trong một số trường nhưng điều đó không thực sự thay đổi nhiều, bạn luôn luôn kết thúc nhắm đến là tốt nhất bạn có thể làm trong những hạn chế của nền tảng này không có vấn đề gì nền tảng là.


Tôi không biết gì về Arduinos nhưng làm rất nhiều việc phát triển máy tính để bàn và máy chủ. Có một cách sử dụng được chấp nhận (IMHO) cho gotos và đó là thoát ra khỏi các vòng lặp lồng nhau, nó sạch sẽ và dễ hiểu hơn nhiều so với các lựa chọn thay thế.
Kiên trì

Nếu bạn nghĩ rằng gotođó là xấu xa, hãy kiểm tra mã Linux. Có thể cho rằng, chúng chỉ xấu như try...catchkhối khi được sử dụng như vậy.
Dmitry Grigoryev

5

Họ có ác không? Có lẽ. Vấn đề với toàn cầu là chúng có thể được truy cập và sửa đổi tại bất kỳ thời điểm nào bởi bất kỳ chức năng hoặc đoạn mã nào được thực thi, mà không bị hạn chế. Điều này có thể dẫn đến các tình huống, giả sử, khó theo dõi và giải thích. Do đó, tối thiểu hóa số lượng toàn cầu, nếu có thể đưa số tiền trở về 0, do đó là mong muốn.

Họ có thể tránh được không? Hầu như luôn luôn có. Vấn đề với Arduino là, họ buộc bạn phải tiếp cận hai chức năng này trong đó họ giả định bạn setup()và bạn loop(). Trong trường hợp cụ thể này, bạn không có quyền truy cập vào phạm vi của chức năng người gọi của hai chức năng này (có thể main()). Nếu bạn có, bạn sẽ có thể thoát khỏi tất cả các thế giới và sử dụng người địa phương thay thế.

Hình ảnh như sau:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Điều này có lẽ ít nhiều là chức năng chính của chương trình Arduino trông như thế nào. Các biến bạn cần trong cả hàm setup()loop()hàm sau đó tốt nhất sẽ được khai báo bên trong phạm vi của main()hàm thay vì phạm vi toàn cục. Sau đó, chúng có thể được truy cập vào hai hàm khác bằng cách chuyển chúng dưới dạng đối số (sử dụng con trỏ nếu cần thiết).

Ví dụ:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Lưu ý rằng trong trường hợp này, bạn cũng cần thay đổi chữ ký của cả hai chức năng.

Vì điều này có thể không khả thi và cũng không mong muốn, tôi thực sự chỉ có một cách để loại bỏ hầu hết các phần mềm toàn cầu khỏi chương trình Arduino mà không sửa đổi cấu trúc chương trình bắt buộc.

Nếu tôi nhớ lại một cách chính xác, bạn hoàn toàn có thể sử dụng C ++ trong khi lập trình cho Arduino, thay vì C. Nếu bạn chưa quen (chưa) với OOP (Lập trình hướng đối tượng) hoặc C ++, có thể bạn sẽ quen với một số đọc hiểu.

Đề xuất của tôi sẽ là tạo một lớp Chương trình và tạo một thể hiện toàn cầu duy nhất của lớp này. Một lớp nên được coi là bản thiết kế cho các đối tượng.

Hãy xem xét chương trình ví dụ sau:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, chúng ta đã loại bỏ hầu hết tất cả các thế giới. Các hàm trong đó bạn sẽ bắt đầu thêm logic ứng dụng của mình sẽ là Program::setup()và các Program::loop()hàm. Các hàm này có quyền truy cập vào các biến thành viên cụ thể của cá thể myFirstSampleVariablemySecondSampleVariabletrong khi các hàm truyền thống setup()loop()hàm không có quyền truy cập vì các biến này đã được đánh dấu là lớp riêng. Khái niệm này được gọi là đóng gói dữ liệu hoặc ẩn dữ liệu.

Dạy cho bạn OOP và / hoặc C ++ là một chút ngoài phạm vi của câu trả lời cho câu hỏi này vì vậy tôi sẽ dừng ở đây.

Tóm lại: nên tránh toàn cầu và hầu như luôn luôn có thể giảm đáng kể số lượng toàn cầu. Ngoài ra khi bạn đang lập trình cho Arduino.

Quan trọng nhất là tôi hy vọng câu trả lời của tôi có phần hữu ích với bạn :)


Bạn có thể định nghĩa main () của riêng bạn trong bản phác thảo nếu bạn thích. Đây là những gì các cổ phiếu một vẻ ngoài như: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/...
per1234

Một singleton thực sự là một toàn cầu, chỉ cần ăn mặc theo một cách khó hiểu thêm. Nó có những nhược điểm tương tự.
bắt đầu

@patstew Bạn có phiền khi giải thích cho tôi bạn cảm thấy nó có những nhược điểm như thế nào không? Theo tôi thì không phải vì bạn có thể sử dụng đóng gói dữ liệu vì lợi ích của bạn.
Arjen

@ per1234 Cảm ơn! Tôi chắc chắn không phải là chuyên gia về Arduino, nhưng tôi cho rằng đề xuất đầu tiên của tôi cũng có thể hoạt động.
Arjen

2
Chà, nó vẫn là trạng thái toàn cầu có thể được truy cập ở bất cứ đâu trong chương trình, bạn chỉ cần truy cập nó Program::instance().setup()thay vì globalProgram.setup(). Đặt các biến toàn cục có liên quan vào một không gian lớp / struct / tên có thể có lợi, đặc biệt nếu chúng chỉ cần một vài hàm liên quan, nhưng mẫu singleton không thêm bất cứ thứ gì. Nói cách khác, static Program p;có lưu trữ toàn cầu và static Program& instance()có quyền truy cập toàn cầu, tương đương với đơn giản Program globalProgram;.
bắt đầu

4

Biến toàn cầu không bao giờ là xấu xa . Một quy tắc chung chống lại họ chỉ là một cái nạng để cho phép bạn tồn tại đủ lâu để có được kinh nghiệm để đưa ra quyết định tốt hơn.

Biến toàn cầu là gì, là một giả định cố hữu rằng chỉ có một thứ duy nhất (không quan trọng nếu chúng ta đang nói về một mảng hoặc bản đồ toàn cầu có thể chứa nhiều thứ, vẫn chứa giả định rằng chỉ có một danh sách hoặc ánh xạ như vậy, và không nhiều danh sách độc lập).

Vì vậy, trước khi bạn sử dụng toàn cầu, bạn muốn tự hỏi: có thể hiểu được rằng tôi sẽ muốn sử dụng nhiều hơn một trong những thứ này không? Nếu nó trở thành sự thật, bạn sẽ phải sửa đổi mã để không toàn cầu hóa điều đó và có thể bạn sẽ tìm ra cách mà các phần khác trong mã của bạn phụ thuộc vào giả định đó là duy nhất, vì vậy bạn Sẽ phải sửa chúng, và quá trình này trở nên tẻ nhạt và dễ bị lỗi. "Đừng sử dụng toàn cầu" được dạy bởi vì thông thường, đó là một chi phí khá nhỏ để tránh toàn cầu ngay từ đầu và nó tránh được khả năng phải trả một chi phí lớn sau này.

Nhưng các giả định đơn giản hóa mà toàn cầu cho phép cũng làm cho mã của bạn nhỏ hơn, nhanh hơn và sử dụng ít bộ nhớ hơn, bởi vì nó không phải vượt qua một khái niệm về thứ mà nó sử dụng, không phải làm theo hướng, không cần phải làm xem xét khả năng của điều mong muốn có thể không tồn tại, v.v. Khi nhúng, bạn có nhiều khả năng bị hạn chế về kích thước mã và / hoặc thời gian CPU và / hoặc bộ nhớ so với trên PC, vì vậy những khoản tiết kiệm này có thể quan trọng. Và nhiều ứng dụng nhúng cũng có độ cứng hơn trong các yêu cầu - bạn biết đấy rằng chip của bạn chỉ có một trong một thiết bị ngoại vi nhất định, người dùng không thể cắm một cái khác vào cổng USB hoặc một cái gì đó.

Một lý do phổ biến khác để muốn nhiều hơn một thứ có vẻ là duy nhất là thử nghiệm - kiểm tra sự tương tác giữa hai thành phần dễ dàng hơn khi bạn có thể chuyển một phiên bản thử nghiệm của một thành phần nào đó sang một chức năng, trong khi cố gắng sửa đổi hành vi của một thành phần toàn cầu là một đề xuất phức tạp hơn. Nhưng thử nghiệm trong thế giới nhúng có xu hướng rất khác với các nơi khác, vì vậy điều này có thể không áp dụng cho bạn. Theo tôi biết, Arduino không có văn hóa thử nghiệm nào.

Vì vậy, đi trước và sử dụng toàn cầu khi chúng có vẻ đáng giá. Cảnh sát mật mã sẽ không đến và giúp bạn. Chỉ cần biết rằng lựa chọn sai có thể dẫn đến nhiều công việc hơn cho bạn, vì vậy nếu bạn không chắc chắn ...


0

Là biến toàn cầu ác trong Arduino?

không có gì là xấu xa, bao gồm các biến toàn cầu. Tôi sẽ mô tả nó như là một "điều ác cần thiết" - nó có thể làm cho cuộc sống của bạn dễ dàng hơn nhiều nhưng cần thận trọng khi tiếp cận.

Ngoài ra những cách thực hành nào tôi có thể sử dụng để giảm số lượng toàn cầu hơn nữa?

sử dụng các hàm bao bọc để truy cập các biến toàn cục của bạn. vì vậy bạn ít nhất đang quản lý nó từ quan điểm phạm vi.


3
Nếu bạn sử dụng các hàm bao bọc để truy cập các biến toàn cục, bạn cũng có thể đặt các biến của mình bên trong các hàm này.
Dmitry Grigoryev
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.