Sự khác biệt giữa khai báo một biến bên ngoài vòng lặp và khai báo tĩnh bên trong vòng lặp là gì?


9

Đây là hai cách tôi có thể giữ một biến ngoài vòng lặp (hoặc bất kỳ hàm nào).

Đầu tiên, tôi có thể khai báo nó với phạm vi toàn cầu bên ngoài vòng lặp:

void setup()
{
    Serial.begin(9600);
}

int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    delay(250);
}

Tôi cũng có thể khai báo nó tĩnh bên trong vòng lặp:

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    static int count = 0;

    Serial.println(count);
    count++;

    delay(250);
}

Điều gì khác biệt, nếu có, điều này sẽ làm cho?

Câu trả lời:


10

Sự khác biệt cơ bản nhất là về phạm vi.

Trong trường hợp đầu tiên, bạn đang khai báo một biến toàn cục. Đó là một biến có thể truy cập trong mọi phạm vi sau định nghĩa của nó.

void setup()
{
    Serial.begin(9600);
}

void inc();
int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

void inc() //Can edit the value of count
{
  count=count+1;
};

Trong trường hợp thứ hai, bạn đang khai báo một biến tĩnh có phạm vi cục bộ. Biến sẽ tồn tại cho toàn bộ chương trình chạy tương tự như biến toàn cục, nhưng sẽ chỉ có thể truy cập được trong khối mã được khai báo. Đây là ví dụ tương tự, chỉ có một thay đổi. countbây giờ được khai báo là một biến tĩnh bên trong loop.

void inc();

void loop()
{
    static int count = 0;
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

Điều này sẽ không biên dịch vì hàm inc()không có quyền truy cập count.

Các biến toàn cầu, tuy có vẻ hữu ích, đi kèm với một số cạm bẫy. Những thứ này thậm chí có thể gây ra thiệt hại khi viết các chương trình có thể tương tác với môi trường xung quanh. Đây là một ví dụ rất cơ bản về một cái gì đó rất có thể xảy ra, ngay khi các chương trình bắt đầu lớn hơn. Một hàm có thể vô tình thay đổi trạng thái của một biến toàn cục.

void setup()
{
    Serial.begin(9600);
}
void another_function();
int state=0;

void loop()
{
    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Inadvertently changes state
  state=1;

}

Những trường hợp như vậy rất khó để gỡ lỗi. Đây loại vấn đề tuy nhiên, có thể dễ dàng được phát hiện, bằng cách đơn giản sử dụng một biến tĩnh.

void setup()
{
    Serial.begin(9600);
}
void another_function();

void loop()
{
    static int state=0;

    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Results in a compile time error. Saves time.
  state=1;

}

5

Từ góc độ chức năng, cả hai phiên bản đều tạo ra cùng một kết quả, vì trong cả hai trường hợp, giá trị của countđược lưu trữ giữa các lần thực thi của loop()(vì nó là biến toàn cục hoặc do nó được đánh dấu staticvà do đó giữ giá trị của nó).

Vì vậy, quyết định chọn đi xuống theo các đối số sau:

  1. Nói chung, trong khoa học máy tính, khuyến khích giữ các biến của bạn càng cục bộ càng tốt về phạm vi . Điều này thường dẫn đến mã rõ ràng hơn với ít tác dụng phụ hơn và giảm khả năng người khác sử dụng biến toàn cục đó làm hỏng logic của bạn). Ví dụ, trong ví dụ đầu tiên của bạn, các khu vực logic khác có thể thay đổi countgiá trị, trong khi trong lần thứ hai, chỉ có chức năng cụ loop()thể đó có thể làm như vậy).
  2. Các biến toàn cục và tĩnh luôn chiếm bộ nhớ , trong đó như người dân địa phương chỉ làm khi chúng ở trong phạm vi. Trong các ví dụ trên của bạn không có sự khác biệt (vì trong một bạn sử dụng biến toàn cục, trong biến khác là biến tĩnh), nhưng trong các chương trình lớn hơn và phức tạp hơn có thể và bạn có thể lưu bộ nhớ bằng cách sử dụng cục bộ không tĩnh. Tuy nhiên : Nếu bạn có một biến trong vùng logic được thực thi rất thường xuyên, hãy xem xét biến nó thành tĩnh hoặc toàn cục, vì nếu không, bạn sẽ mất một chút hiệu suất mỗi khi vùng logic đó được nhập, vì phải mất một chút thời gian để cấp phát bộ nhớ cho trường hợp biến mới đó. Bạn cần tìm sự cân bằng giữa tải bộ nhớ và hiệu suất.
  3. Các điểm khác như bố trí tốt hơn để phân tích tĩnh hoặc tối ưu hóa bởi trình biên dịch cũng có thể đi vào hoạt động.
  4. Trong một số trường hợp đặc biệt, có thể có vấn đề với thứ tự khởi tạo không thể đoán trước của các phần tử tĩnh (không chắc chắn về điểm đó, mặc dù vậy hãy so sánh liên kết này ).

Nguồn: Chủ đề tương tự trên arduino.cc


Việc đăng ký lại không bao giờ là vấn đề đối với Arduino vì nó không hỗ trợ đồng thời.
Peter Bloomfield

Thật. Đó là nhiều hơn một điểm chung, nhưng thực sự không liên quan đến Arduino. Tôi đã loại bỏ bit đó.
Philip Allgaier

1
Một biến tĩnh được khai báo bên trong một phạm vi sẽ luôn tồn tại và sử dụng cùng một không gian như một biến toàn cục! Trong mã OP, sự khác biệt duy nhất là mã nào có thể truy cập vào biến. Trong scipe tĩnh wil có thể truy cập trong cùng phạm vi.
jfpoilpret

1
@jfpoilpret Điều đó tất nhiên là đúng, và tôi thấy rằng phần tương ứng trong câu trả lời của tôi là một chút sai lệch. Đã sửa lỗi đó.
Philip Allgaier

2

Cả hai biến đều tĩnh - chúng tồn tại trong toàn bộ phiên thực hiện. Toàn cầu hiển thị cho bất kỳ chức năng nào nếu nó khai báo - không xác định - toàn cục hoặc nếu chức năng tuân theo định nghĩa trong cùng một đơn vị biên dịch (tệp + bao gồm).

Việc chuyển định nghĩa countsang bên trong hàm vừa giới hạn phạm vi hiển thị của nó sang tập {}es kèm theo gần nhất , vừa mang lại cho nó thời gian gọi hàm (nó được tạo và hủy khi hàm được nhập và thoát). Tuyên bố nó staticcũng cung cấp cho nó phiên thực hiện trọn đời, nó tồn tại từ đầu đến cuối phiên thực hiện, vẫn tồn tại trên các lệnh gọi hàm.

BTW: hãy thận trọng về việc sử dụng các trạng thái khởi tạo trong một hàm, vì tôi đã thấy một số phiên bản của trình biên dịch gnu đã sai. Một biến tự động với bộ khởi tạo nên được tạo và khởi tạo trên mỗi mục nhập chức năng. Một tĩnh với trình khởi tạo chỉ nên được khởi tạo một lần, trong quá trình thiết lập thực thi, trước khi hàm main () được cấp quyền điều khiển (giống như toàn cục). Tôi đã có thống kê cục bộ được khởi tạo lại trên mỗi mục nhập chức năng như thể chúng là tự động, điều này không chính xác. Kiểm tra trình biên dịch của riêng bạn để chắc chắn.


Tôi không chắc tôi hiểu ý của bạn về chức năng khai báo toàn cầu. Bạn có nghĩa là một extern?
Peter Bloomfield

@ PeterR.Bloomfield: Tôi không chắc phần nào trong bài đăng của bạn hỏi về tôi, nhưng tôi đã đề cập đến hai ví dụ của OP - thứ nhất, một định nghĩa toàn cầu vốn có và thứ hai, một tĩnh cục bộ.
JRobert

-3

Theo tài liệu của Atmel: "Nếu một biến toàn cục được khai báo, một địa chỉ duy nhất trong SRAM sẽ được gán cho biến này tại thời điểm liên kết chương trình."

Tài liệu đầy đủ có ở đây (Mẹo số 2 cho các biến toàn cục): http://www.atmel.com/images/doc8453.pdf


4
Cả hai ví dụ sẽ kết thúc với một địa chỉ duy nhất trong SRAM? Cả hai cần phải kiên trì.
Cyberg Ribbon

2
Có thực sự bạn có thể tìm thấy thông tin đó trong cùng một tài liệu trong mẹo số 6
jfpoilpret
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.