Làm thế nào là bộ nhớ ngăn xếp được sử dụng cho các phiên bản và các biến cục bộ?


8

Tôi muốn lưu một số giá trị vào EEPROM và cũng muốn giải phóng SRAM bằng cách tránh một số khai báo biến, nhưng bộ nhớ EEPROM là byte khôn ngoan.

Nếu tôi muốn lưu trữ một giá trị int, tôi phải sử dụng một số biểu thức nhiều lần. Tôi nghĩ rằng tôi sẽ làm cho một số chức năng cho những người. Nhưng tôi lo ngại rằng, nếu tôi tạo một hàm, nó vẫn chiếm bộ nhớ SRAM, tốt hơn là tôi khai báo một biến int thay vì sử dụng EEPROM.

Các hàm và các biến cục bộ được lưu trữ trong SRAM như thế nào? Có phải nó chỉ lưu địa chỉ của con trỏ kết hợp từ bộ nhớ flash hoặc tất cả các biến và lệnh được lưu trữ trên ngăn xếp?


4
Hãy nhớ rằng EEPROM chỉ có thể ghi trong một số lần giới hạn, đọc nó là không giới hạn. Theo bảng dữ liệu EEPROM của AVR chỉ có 100000 chu kỳ, nghe có vẻ nhiều nhưng khi bạn cố gắng sử dụng nó như SRAM, nó sẽ chỉ tồn tại trong một khoảng thời gian khá ngắn.
jippie

CHÚA ƠI! Sau đó, EEPROM sẽ vô dụng? Tôi sẽ kiểm tra bảng dữ liệu!
Nafis

Bộ nhớ Flash cũng có vòng đời. Sẽ khôn ngoan hơn khi không đốt chương trình nhiều.
Nafis

Với việc sử dụng bình thường, các số được cung cấp cho flash và EEPROM hoàn toàn không có vấn đề gì. Phương trình thay đổi khi bạn bắt đầu sử dụng nó giống như bạn sử dụng SRAM.
jippie

Câu trả lời:


4

Chỉ dữ liệu của hàm được lưu trữ trên ngăn xếp; mã của nó vẫn ở trong flash. Thay vào đó, bạn thực sự không thể giảm sử dụng SRAM bằng cách sử dụng EEPROM vì như bạn đã thấy, EEPROM không thể truy cập theo cùng một cách. Mã để đọc và lưu trữ EEPROM cũng cần sử dụng một số SRAM - có thể nhiều như SRAM như bạn đang cố lưu! EEPROM cũng chậm viết và có tuổi thọ giới hạn (về số lần ghi vào mỗi byte), cả hai đều khiến nó không thực tế khi sử dụng để lưu trữ loại dữ liệu tạm thời mà chúng ta thường đưa vào ngăn xếp. Nó phù hợp hơn với việc lưu dữ liệu thay đổi không thường xuyên, như cấu hình thiết bị duy nhất cho các thiết bị được sản xuất hàng loạt hoặc ghi lại các lỗi không thường xuyên để phân tích sau này.

Đã chỉnh sửa: Không có ngăn xếp cho chức năng đó cho đến khi chức năng được gọi, vì vậy, đó là khi bất kỳ dữ liệu nào của chức năng được đặt ở đó. Điều xảy ra sau khi hàm trả về là khung stack của nó (vùng SRAM dành riêng của nó) không còn được bảo lưu. Cuối cùng nó sẽ được sử dụng lại bởi một lệnh gọi hàm khác. Dưới đây là sơ đồ của ngăn xếp C trong bộ nhớ. Khi một khung ngăn xếp không còn hữu ích, nó chỉ được giải phóng và bộ nhớ của nó sẽ được sử dụng lại.


Tôi đang nghĩ theo cách này, khi hàm được gọi, chỉ sau đó dữ liệu bên trong nó được lưu trữ trong ngăn xếp. Sau khi thực hiện chức năng, dữ liệu sẽ bị xóa khỏi stack / SRAM. Tôi có đúng không
Nafis

5

Các biến cục bộ và tham số hàm được lưu trữ trên ngăn xếp. Tuy nhiên, đó không phải là lý do để không sử dụng chúng. Máy tính được thiết kế để làm việc theo cách đó.

Bộ nhớ ngăn xếp chỉ được sử dụng trong khi một chức năng đang hoạt động. Ngay khi hàm trả về, bộ nhớ sẽ được giải phóng. Bộ nhớ ngăn xếp là một điều tốt.

Bạn không muốn sử dụng các hàm đệ quy với nhiều mức đệ quy hoặc phân bổ nhiều cấu trúc lớn trên ngăn xếp. Sử dụng bình thường là tốt tuy nhiên.

Ngăn xếp 6502 chỉ 256 byte, nhưng Apple II hoạt động tốt.


Vì vậy, ý bạn là hàm sẽ được lưu với tất cả các biến, tham số và biểu thức cục bộ của nó vào ngăn xếp tạm thời, chỉ khi nó được gọi? Nếu không nó sẽ ở trong chương trình / bộ nhớ flash? Sau khi thực hiện, nó sẽ bị xóa khỏi stack? Tôi đã nói về Arduino thực sự, vì nó là Diễn đàn Arduino, tôi đã không đề cập đến điều đó.
Nafis

Không, chỉ có các tham số và biến cục bộ của hàm nằm trên ngăn xếp. Mã của hàm không được lưu trên ngăn xếp. Đừng nghĩ quá nhiều về điều này.
Duncan C

5

AVR (họ vi điều khiển thường được sử dụng trên bo mạch Arduino) là một Kiến trúc Harvard , có nghĩa là mã và các biến thực thi nằm trong hai bộ nhớ riêng biệt - trong trường hợp này là flash và SRAM. Mã thực thi không bao giờ để lại bộ nhớ flash.

Khi bạn gọi một chức năng, địa chỉ trả về thường được đẩy đến ngăn xếp - ngoại lệ là khi cuộc gọi chức năng xảy ra ở cuối chức năng gọi. Trong trường hợp này, địa chỉ trả về của hàm được gọi là hàm gọi sẽ được sử dụng thay thế - nó đã có trên ngăn xếp.
Việc có bất kỳ dữ liệu nào khác được đưa vào ngăn xếp hay không phụ thuộc vào áp suất thanh ghi trong chức năng gọi và trong chức năng được gọi. Các thanh ghi là vùng làm việc của CPU, AVR có 32 thanh ghi 1 byte. Các thanh ghi có thể được truy cập trực tiếp bằng các hướng dẫn CPU, trong khi dữ liệu trong SRAM trước tiên sẽ phải được lưu trữ trong các thanh ghi. Chỉ khi các đối số hoặc biến cục bộ quá lớn hoặc quá nhiều để phù hợp với các thanh ghi thì chúng mới được đưa vào ngăn xếp. Tuy nhiên, cấu trúc luôn được lưu trữ trên ngăn xếp.

Bạn có thể đọc chi tiết về cách ngăn xếp được trình biên dịch GCC sử dụng trên nền tảng AVR tại đây: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Đọc các phần "Bố cục khung" và "Quy ước gọi" .


1

Ngay lập tức khi một lệnh gọi hàm nhập vào hàm, mã đầu tiên được thực thi là giảm số lượng ngăn xếp theo số lượng bằng với không gian cần thiết cho các biến tạm thời bên trong hàm. Điều tuyệt vời ở đây là tất cả các hàm do đó trở thành đăng ký lại và đệ quy, bởi vì các biến của chúng được xây dựng trên ngăn xếp của chương trình gọi. Điều đó có nghĩa là nếu một ngắt dừng thực thi một chương trình và chuyển thực thi sang một chương trình khác, thì nó cũng có thể gọi cùng một chức năng mà không cần chúng can thiệp vào nhau.


1

Tôi đã rất cố gắng để tạo ra một đoạn mã ví dụ để chứng minh những câu trả lời xuất sắc ở đây đang nói gì, mà không thành công cho đến nay. Lý do là trình biên dịch tích cực tối ưu hóa mọi thứ. Cho đến nay các thử nghiệm của tôi đã không sử dụng ngăn xếp, ngay cả với các biến cục bộ trong một hàm. Những lý do là:


  • Trình biên dịch có thể nội tuyến gọi hàm, do đó địa chỉ trả về có thể không được đẩy lên ngăn xếp. Thí dụ:

    void foo (byte a) { digitalWrite (13, a); } void loop () { foo (5); }

    Trình biên dịch biến nó thành:

    void loop () { digitalWrite (13, 5); }

    Không có chức năng gọi, không sử dụng ngăn xếp.


  • Trình biên dịch có thể truyền các đối số trong các thanh ghi , do đó lưu nó phải đẩy chúng lên ngăn xếp. Thí dụ:

    digitalWrite (13, 1);

    Biên dịch thành:

    158: 8d e0 ldi r24, 0x0D ; 13 15a: 61 e0 ldi r22, 0x01 ; 1 15c: 0e 94 05 01 call 0x20a ; 0x20a <digitalWrite>

    Các đối số được đưa vào các thanh ghi và do đó không có ngăn xếp nào được sử dụng (ngoài địa chỉ trả về để gọi digitalWrite).


  • Các biến cục bộ cũng có thể được đưa vào các thanh ghi, một lần nữa tiết kiệm phải sử dụng RAM. Điều này không chỉ tiết kiệm RAM mà còn nhanh hơn.

  • Trình biên dịch tối ưu hóa các biến bạn không sử dụng. Thí dụ:

    void foo (byte a) { unsigned long bar [100]; bar [1] = a; digitalWrite (9, bar [1]); } void loop () { foo (3); } // end of loop

    Bây giờ nhân đã phân bổ 400 byte cho "thanh" phải không? Không

    00000100 <_Z3fooh>: 100: 68 2f mov r22, r24 102: 89 e0 ldi r24, 0x09 ; 9 104: 0e 94 cd 00 call 0x19a ; 0x19a <digitalWrite> 108: 08 95 ret 0000010a <loop>: 10a: 83 e0 ldi r24, 0x03 ; 3 10c: 0e 94 80 00 call 0x100 ; 0x100 <_Z3fooh> 110: 08 95 ret

    Trình biên dịch tối ưu hóa toàn bộ mảng ! Nó có thể nói rằng chúng ta thực sự chỉ đang làm một digitalWrite (9, 3)và đó là những gì nó tạo ra.


Đạo đức của câu chuyện: Đừng cố nghĩ ra trình biên dịch.


Hầu hết các hàm không tầm thường sử dụng ngăn xếp để lưu một số thanh ghi, do đó chúng có thể được sử dụng để giữ các biến cục bộ. Sau đó, chúng ta có một tình huống buồn cười trong đó khung ngăn xếp của hàm chứa các biến cục bộ thuộc về trình gọi của nó .
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.