Bagaimana tumpukan memori digunakan untuk fungsi dan variabel lokal?


Saya ingin menyimpan beberapa nilai ke EEPROM dan juga ingin membebaskan SRAM dengan menghindari beberapa deklarasi variabel, tetapi memori EEPROM adalah byte yang bijak.

Jika saya ingin menyimpan nilai int, saya harus menggunakan beberapa ekspresi berulang kali. Saya pikir saya akan membuat beberapa fungsi untuk itu. Tetapi saya khawatir bahwa, jika saya membuat suatu fungsi, ia masih akan menempati memori SRAM, lebih baik saya mendeklarasikan variabel int daripada menggunakan EEPROM.

Bagaimana fungsi dan variabel lokal disimpan dalam SRAM? Apakah itu hanya menyimpan alamat penunjuk fungsinya dari memori flash atau semua variabel dan perintah disimpan di stack?


4
Ingatlah bahwa EEPROM hanya dapat ditulis untuk beberapa kali, membacanya tidak terbatas. Menurut datasheet AVR EEPROM hanya memiliki 100000 siklus, yang terdengar banyak tetapi ketika Anda mencoba menggunakannya sebagai SRAM, itu hanya akan berlangsung dalam waktu yang cukup singkat.
jippie

OH TUHAN! Setelah itu, akankah EEPROM menjadi tidak berguna? Saya akan memeriksa lembar data!
Nafis

Memori Flash juga memiliki gaya hidup. Lebih bijaksana untuk tidak membakar banyak program.
Nafis

Dengan penggunaan normal angka-angka yang diberikan untuk flash dan EEPROM tidak ada masalah sama sekali. Persamaan berubah ketika Anda mulai menggunakannya seperti Anda menggunakan SRAM.
jippie

Jawaban:


Hanya data fungsi yang disimpan di tumpukan; kodenya tetap dalam flash. Anda tidak dapat benar-benar mengurangi penggunaan SRAM dengan menggunakan EEPROM sebagai gantinya karena, seperti yang telah Anda lihat, EEPROM tidak dapat dialamatkan dengan cara yang sama. Kode untuk membaca dan menyimpan EEPROM juga perlu menggunakan beberapa SRAM - mungkin sebanyak SRAM yang Anda coba hemat! EEPROM juga lambat untuk menulis, dan memiliki masa pakai yang terbatas (dalam jumlah penulisan untuk setiap byte), yang keduanya membuatnya tidak praktis untuk digunakan untuk menyimpan jenis data sementara yang biasanya kita letakkan di tumpukan. Ini lebih cocok untuk menyimpan data yang jarang diubah, seperti konfigurasi perangkat unik untuk perangkat yang diproduksi secara massal, atau menangkap kesalahan yang jarang terjadi untuk analisis nanti.

Diedit: Tidak ada tumpukan untuk fungsi itu sampai fungsi dipanggil, jadi ya, saat itulah salah satu data fungsi diletakkan di sana. Apa yang terjadi setelah fungsi kembali adalah bahwa stack-frame-nya (area yang dipesan dari SRAM) tidak lagi dicadangkan. Pada akhirnya akan digunakan kembali oleh pemanggilan fungsi lain. Berikut adalah diagram tumpukan C dalam memori. Ketika bingkai tumpukan tidak lagi berguna, itu hanya dilepaskan dan memorinya menjadi tersedia untuk digunakan kembali.


Saya berpikir seperti ini, ketika fungsi dipanggil, hanya data di dalamnya yang disimpan dalam stack. Setelah eksekusi fungsi, data dihapus dari stack / SRAM. Apakah saya benar?
Nafis

Variabel lokal dan parameter fungsi disimpan di tumpukan. Namun, itu bukan alasan untuk tidak menggunakannya. Komputer dirancang untuk bekerja seperti itu.

Memori tumpukan hanya digunakan saat suatu fungsi aktif. Segera setelah fungsi kembali, memori dibebaskan. Stack memory adalah GOOD Thing.

Anda tidak ingin menggunakan fungsi rekursif dengan banyak tingkat rekursi, atau mengalokasikan banyak struktur besar di stack. Namun penggunaan normal baik-baik saja.

Tumpukan 6502 hanya 256 byte, tetapi Apple II berfungsi dengan baik.


Jadi, maksud Anda fungsi tersebut akan disimpan dengan semua variabel lokal, parameter, dan ekspresi ke stack sementara, hanya ketika dipanggil? Kalau tidak, ia akan tetap dalam memori program / flash? Setelah eksekusi, apakah akan dihapus dari tumpukan? Saya sebenarnya berbicara tentang Arduino, karena ini adalah Forum Arduino, saya tidak menyebutkannya.
Nafis

Tidak, hanya parameter fungsi dan variabel lokal yang ada di tumpukan. Kode fungsi tidak disimpan di tumpukan. Jangan terlalu memikirkan ini.
Duncan C

AVR (keluarga mikrokontroler yang biasanya digunakan pada papan Arduino) adalah Arsitektur Harvard , yang berarti bahwa kode dan variabel yang dapat dieksekusi berada dalam dua memori terpisah - dalam hal ini flash dan SRAM. Kode yang dapat dieksekusi tidak pernah meninggalkan memori flash.

Ketika Anda memanggil suatu fungsi, alamat pengirim biasanya didorong ke tumpukan - pengecualiannya adalah ketika panggilan fungsi terjadi di akhir fungsi panggilan. Dalam hal ini alamat pengirim fungsi yang disebut fungsi panggilan akan digunakan - itu sudah ada di tumpukan.
Apakah ada data lain yang diletakkan di tumpukan tergantung pada tekanan register dalam fungsi panggilan dan dalam fungsi yang dipanggil. Register adalah area kerja CPU, AVR memiliki 32 register 1 byte. Register dapat diakses langsung dengan instruksi CPU, sedangkan data dalam SRAM pertama-tama harus disimpan dalam register. Hanya jika argumen atau variabel lokal terlalu besar atau terlalu banyak untuk masuk register mereka akan diletakkan di tumpukan. Namun, struktur selalu disimpan di tumpukan.

Anda dapat membaca detail tentang bagaimana tumpukan digunakan oleh kompiler GCC pada platform AVR di sini: https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout
Baca bagian "Tata Letak Bingkai" dan "Konvensi Pemanggilan" .


Segera setelah pemanggilan fungsi memasuki fungsi, kode pertama yang dieksekusi adalah untuk mengurangi stackpointer dengan jumlah yang sama dengan ruang yang diperlukan untuk variabel sementara internal ke fungsi. Hal yang brilian tentang ini adalah bahwa semua fungsi karenanya menjadi kembali masuk dan rekursif, karena variabel mereka dibangun di atas tumpukan program panggilan. Itu berarti jika suatu interupsi menghentikan eksekusi dari satu program dan mentransfer eksekusi ke yang lain, itu juga dapat memanggil fungsi yang sama tanpa mereka mengganggu satu sama lain.


Saya sudah berusaha cukup keras untuk membuat contoh sedikit kode untuk menunjukkan apa jawaban yang sangat baik di sini, tanpa keberhasilan sejauh ini. Alasannya adalah bahwa kompiler secara agresif mengoptimalkan berbagai hal. Sejauh ini pengujian saya belum menggunakan stack sama sekali, bahkan dengan variabel lokal dalam suatu fungsi. Alasannya adalah:


  • Compiler mungkin in-line panggilan fungsi, sehingga alamat pengirim mungkin tidak didorong ke stack sama sekali. Contoh:

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

    Kompiler mengubahnya menjadi:

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

    Tidak ada panggilan fungsi, tidak ada tumpukan yang digunakan.


  • Kompiler dapat meneruskan argumen dalam register , sehingga menyimpannya harus mendorong mereka ke tumpukan. Contoh:

    digitalWrite (13, 1);

    Kompilasi menjadi:

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

    Argumen dimasukkan ke dalam register dan karenanya tidak ada tumpukan yang digunakan (terlepas dari alamat pengirim untuk memanggil digitalWrite).


  • Variabel lokal mungkin dimasukkan ke dalam register, lagi-lagi menghemat karena harus menggunakan RAM. Ini tidak hanya menghemat RAM tetapi lebih cepat.

  • Kompiler mengoptimalkan variabel jauh yang tidak Anda gunakan. Contoh:

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

    Nah, itu harus mengalokasikan 400 byte untuk "bar" bukan? Nggak:

    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

    Kompiler mengoptimalkan seluruh array ! Dapat digitalWrite (9, 3)dikatakan bahwa kita benar-benar hanya melakukan dan itulah yang dihasilkannya.


Moral dari cerita ini: Jangan mencoba untuk tidak memikirkan kompiler.


Sebagian besar fungsi non-sepele menggunakan stack untuk menyimpan beberapa register, sehingga ini dapat digunakan untuk menyimpan variabel lokal. Kemudian kita memiliki situasi lucu ini di mana fungsi stack frame berisi variabel lokal milik pemanggilnya .
Edgar Bonet
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.