Penggunaan variabel global dalam Sistem Tertanam

17

Saya mulai menulis firmware untuk produk saya dan saya pemula di sini. Saya membaca banyak artikel tentang tidak menggunakan variabel atau fungsi global. Apakah ada batasan untuk menggunakan variabel global dalam sistem 8 bit atau apakah itu lengkap 'Tidak-Tidak'. Bagaimana saya harus menggunakan variabel global dalam sistem saya atau haruskah saya sepenuhnya menghindarinya?

Saya ingin mengambil saran berharga dari kalian tentang topik ini untuk membuat firmware saya lebih ringkas.

Rookie91
sumber
Pertanyaan ini tidak unik untuk sistem tertanam. Sebuah duplikat dapat ditemukan di sini .
Lundin
@Lundin Dari tautan Anda: "Hari-hari ini yang hanya penting di lingkungan tertanam di mana memori sangat terbatas. Sesuatu yang perlu diketahui sebelum Anda menganggap bahwa tertanam adalah sama dengan lingkungan lain dan menganggap aturan pemrograman sama di seluruh papan."
endolith
@endolith staticruang lingkup file tidak sama dengan "global", lihat jawaban saya di bawah ini.
Lundin

Jawaban:

31

Anda dapat menggunakan variabel global dengan sukses, selama Anda mengingat pedoman @ Phil. Namun, berikut adalah beberapa cara yang bagus untuk menghindari masalah mereka tanpa membuat kode yang dikompilasi kurang kompak.

  1. Gunakan variabel statis lokal untuk status persisten yang hanya ingin Anda akses di dalam satu fungsi.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Gunakan struct untuk menjaga variabel terkait bersama-sama, untuk membuatnya lebih jelas di mana mereka harus digunakan dan di mana tidak.

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. Gunakan variabel statis global untuk membuat variabel hanya terlihat dalam file C saat ini. Ini mencegah akses tidak disengaja oleh kode dalam file lain karena konflik penamaan.

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

Sebagai catatan akhir, jika Anda memodifikasi variabel global dalam rutinitas interupsi dan membacanya di tempat lain:

  • Tandai variabelnya volatile.
  • Pastikan itu adalah atom untuk CPU (yaitu 8-bit untuk CPU 8-bit).

ATAU

  • Gunakan mekanisme penguncian untuk melindungi akses ke variabel.
richarddonkin
sumber
volatile dan / atau vars atom tidak akan membantu Anda menghindari kesalahan, Anda memerlukan semacam kunci / semafor, atau untuk secara singkat menutupi interupsi ketika menulis ke variabel.
John U
3
Itu definisi yang sangat sempit tentang "bekerja dengan baik". Maksud saya adalah bahwa menyatakan sesuatu yang tidak stabil tidak mencegah konflik. Juga, contoh ketiga Anda bukan ide bagus - memiliki dua global yang terpisah dengan nama yang sama setidaknya membuat kode lebih sulit untuk dipahami / dipelihara.
John U
1
@ Johnny Anda tidak boleh menggunakan volatile untuk mencegah dari kondisi balapan, memang itu tidak akan membantu. Anda harus menggunakan volatile untuk mencegah bug optimisasi kompiler berbahaya yang umum dalam kompiler sistem embedded.
Lundin
2
@JohnU: Penggunaan volatilevariabel secara normal adalah untuk memungkinkan kode berjalan dalam satu konteks eksekusi agar kode dalam konteks eksekusi lain tahu sesuatu telah terjadi. Pada sistem 8-bit, buffer yang akan menampung sejumlah dua byte tidak lebih dari 128 dapat dikelola dengan satu byte volatil yang menunjukkan jumlah total seumur hidup byte yang dimasukkan ke buffer (mod 256) dan yang lain menunjukkan jumlah byte yang diambil seumur hidup, asalkan hanya satu konteks eksekusi yang memasukkan data ke buffer dan hanya satu yang mengambil data darinya.
supercat
2
@ JohnU: Meskipun dimungkinkan untuk menggunakan beberapa bentuk penguncian atau menonaktifkan sementara interupsi untuk mengelola buffer, itu benar-benar tidak perlu atau tidak membantu. Jika buffer harus menampung 128-255 byte, coding harus sedikit berubah, dan jika harus menahan lebih dari itu, menonaktifkan interupsi mungkin diperlukan, tetapi pada sistem 8-bit buffer cenderung kecil; sistem dengan buffer yang lebih besar umumnya dapat melakukan penulisan atom yang lebih besar dari 8 bit.
supercat
24

Alasan Anda tidak ingin menggunakan variabel global dalam sistem 8-bit adalah sama dengan yang Anda tidak ingin menggunakannya dalam sistem lain: mereka membuat penalaran tentang perilaku program menjadi sulit.

Hanya programmer buruk yang menutup aturan seperti "jangan gunakan variabel global". Pemrogram yang baik memahami alasan di balik aturan, kemudian memperlakukan aturan lebih seperti panduan.

Apakah program Anda mudah dimengerti? Apakah perilakunya dapat diprediksi? Apakah mudah untuk memodifikasi bagian tanpa merusak bagian lain? Jika jawaban untuk masing-masing pertanyaan ini adalah ya , maka Anda sedang menuju program yang bagus.

Phil Frost
sumber
1
Apa yang dikatakan @MichaelKaras - memahami apa arti hal-hal ini dan bagaimana mereka mempengaruhi hal-hal (dan bagaimana mereka dapat menggigit Anda) adalah hal yang penting.
John U
5

Anda seharusnya tidak sepenuhnya menghindari penggunaan variabel global ("global" singkatnya). Tapi, Anda harus menggunakannya dengan bijaksana. Masalah praktis dengan penggunaan global yang berlebihan:

  • Global terlihat di seluruh unit kompilasi. Kode apa pun di unit kompilasi dapat memodifikasi global. Konsekuensi dari modifikasi dapat muncul di mana saja di mana global ini dievaluasi.
  • Akibatnya global membuat kode lebih sulit untuk dibaca dan dipahami. Programmer harus selalu mengingat semua tempat di mana global dievaluasi dan ditugaskan.
  • Penggunaan global yang berlebihan membuat kode lebih rentan cacat.

Ini adalah praktik yang baik untuk menambahkan awalan g_ke nama variabel global. Sebagai contoh g_iFlags,. Ketika Anda melihat variabel dengan awalan dalam kode, Anda segera mengenali bahwa itu adalah global.

Nick Alexeev
sumber
2
Bendera tidak harus menjadi global. ISR dapat memanggil fungsi yang memiliki variabel statis, misalnya.
Phil Frost
+1 Saya belum pernah mendengar tentang trik seperti itu sebelumnya. Saya telah menghapus paragraf itu dari jawabannya. Bagaimana staticbendera menjadi terlihat oleh main()? Apakah Anda menyiratkan bahwa fungsi yang sama dengan yang staticdapat mengembalikannya ke main()nanti?
Nick Alexeev
Itu salah satu cara untuk melakukannya. Mungkin fungsi tersebut mengambil status baru untuk diatur, dan mengembalikan kondisi lama. Ada banyak cara lain. Mungkin Anda memiliki satu file sumber dengan fungsi untuk mengatur flag, dan yang lainnya untuk mendapatkannya, dengan variabel global statis yang memegang status flag. Meskipun secara teknis ini adalah "global" oleh terminologi C, cakupannya terbatas hanya pada file itu, yang hanya berisi fungsi-fungsi yang perlu diketahui. Artinya, cakupannya bukan "global", yang sebenarnya merupakan masalah. C ++ menyediakan mekanisme enkapsulasi tambahan.
Phil Frost
4
Beberapa metode untuk menghindari global (seperti mengakses perangkat keras hanya melalui driver perangkat) mungkin terlalu tidak efisien untuk lingkungan 8-bit yang sangat kekurangan sumber daya.
Spehro Pefhany
1
@SpehroPefhany Pemrogram yang baik memahami alasan di balik aturan, kemudian memperlakukan aturan lebih seperti panduan. Ketika pedoman bertentangan, programmer yang baik menimbang keseimbangan dengan hati-hati.
Phil Frost
4

Keuntungan dari struktur data global dalam pekerjaan tertanam adalah bahwa mereka statis. Jika setiap variabel yang Anda butuhkan adalah global, maka Anda tidak akan pernah kehabisan memori secara tidak sengaja ketika fungsi dimasukkan dan ruang dibuat untuk mereka di stack. Tetapi kemudian, pada titik itu mengapa memiliki fungsi? Mengapa tidak satu fungsi besar yang menangani semua logika dan proses - seperti program BASIC tanpa GOSUB diizinkan. Jika Anda mengambil ide ini cukup jauh, Anda akan memiliki program bahasa assembly khas dari tahun 1970-an. Efisien, dan tidak mungkin dirawat dan kesulitan menembak.

Jadi gunakan global dengan bijaksana, seperti variabel keadaan (misalnya, jika setiap fungsi perlu tahu apakah sistem berada dalam kondisi interpret atau run) dan struktur data lain yang perlu dilihat oleh banyak fungsi dan seperti yang dikatakan @PhilFrost, adalah perilaku dari fungsi Anda dapat diprediksi? Apakah ada kemungkinan untuk mengisi stack dengan string input yang tidak pernah berakhir? Ini adalah hal-hal untuk desain algoritma.

Perhatikan bahwa statis memiliki arti berbeda di dalam dan di luar fungsi. /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c

C. Towne Springer
sumber
1
Banyak kompiler sistem embedded mengalokasikan variabel otomatis secara statis, tetapi overlay variabel yang digunakan oleh fungsi yang tidak dapat berada dalam ruang lingkup secara bersamaan; ini umumnya menghasilkan penggunaan memori yang sama dengan penggunaan terburuk untuk sistem berbasis tumpukan di mana semua urutan panggilan yang dimungkinkan secara statis sebenarnya bisa terjadi.
supercat
4

Variabel global seharusnya hanya digunakan untuk negara yang benar-benar global. Menggunakan variabel global untuk merepresentasikan sesuatu seperti misalnya garis lintang batas utara peta hanya akan berfungsi jika hanya ada satu "batas utara peta". Jika di masa depan kode mungkin harus bekerja dengan beberapa peta yang memiliki batas utara yang berbeda, kode yang menggunakan variabel global untuk batas utara kemungkinan akan perlu dikerjakan ulang.

Dalam aplikasi komputer biasa, seringkali tidak ada alasan khusus untuk menganggap tidak akan ada lebih dari satu hal. Namun, dalam sistem embedded, asumsi seperti itu seringkali jauh lebih masuk akal. Meskipun ada kemungkinan bahwa program komputer biasa dipanggil untuk mendukung banyak pengguna secara bersamaan, antarmuka pengguna dari sistem tertanam yang khas akan dirancang untuk operasi oleh satu pengguna yang berinteraksi dengan tombol dan tampilan. Dengan demikian, setiap saat akan memiliki status antarmuka pengguna tunggal. Mendesain sistem sehingga banyak pengguna dapat berinteraksi dengan banyak keyboard dan tampilan akan membutuhkan lebih banyak kerumitan, dan membutuhkan waktu lebih lama untuk diimplementasikan, daripada mendesainnya untuk satu pengguna. Jika sistem tidak pernah dipanggil untuk mendukung banyak pengguna, setiap upaya ekstra yang diinvestasikan untuk memfasilitasi penggunaan tersebut akan sia-sia. Kecuali kemungkinan dukungan multi-pengguna akan diperlukan, kemungkinan akan lebih bijaksana jika risiko membuang kode yang digunakan untuk antarmuka pengguna-tunggal jika diperlukan dukungan multi-pengguna, daripada menghabiskan waktu ekstra menambahkan multi-pengguna. dukungan pengguna yang kemungkinan besar tidak akan pernah dibutuhkan.

Faktor terkait dengan sistem tertanam adalah bahwa dalam banyak kasus (terutama yang melibatkan antarmuka pengguna), satu-satunya cara praktis untuk mendukung memiliki lebih dari satu hal adalah dengan menggunakan banyak utas. Dengan tidak adanya beberapa kebutuhan lain untuk multi-threading, mungkin lebih baik menggunakan desain single-threaded sederhana daripada meningkatkan kompleksitas sistem dengan multi-threading yang mungkin tidak pernah benar-benar diperlukan. Jika menambahkan lebih dari satu sesuatu akan memerlukan desain ulang sistem yang besar, tidak masalah jika itu juga membutuhkan pengerjaan ulang penggunaan beberapa variabel global.

supercat
sumber
Jadi menjaga variabel global yang tidak akan berbenturan satu sama lain tidak akan menjadi masalah. misalnya: Day_cntr, week_cntr dll untuk menghitung hari dan minggu masing-masing. Dan saya percaya kita tidak boleh sengaja menggunakan banyak variabel global yang mirip dengan tujuan yang sama dan harus dengan jelas mendefinisikannya. Terima kasih banyak atas respons yang luar biasa. :)
Rookie91
-1

Banyak orang bingung tentang hal ini. Definisi variabel global adalah:

Sesuatu yang dapat diakses dari mana saja di program Anda.

Ini bukan hal yang sama dengan variabel cakupan file , yang dideklarasikan oleh kata kunci static. Itu bukan variabel global, mereka adalah variabel pribadi lokal.

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

Haruskah Anda menggunakan variabel global? Ada beberapa kasus di mana itu baik-baik saja:

Dalam setiap kasus lain, Anda tidak akan pernah menggunakan variabel global. Tidak pernah ada alasan untuk melakukannya. Alih-alih menggunakan variabel lingkup file , yang sangat bagus.

Anda harus berusaha untuk menulis modul kode independen dan otonom yang dirancang untuk melakukan tugas tertentu. Di dalam modul-modul itu, variabel ruang lingkup file internal harus berada sebagai anggota data pribadi. Metode desain ini dikenal sebagai orientasi objek dan secara luas diakui sebagai desain yang baik.

Lundin
sumber
Mengapa downvote?
m .liner
2
Saya pikir "variabel global" juga dapat digunakan untuk menjelaskan alokasi ke segmen global (bukan teks, tumpukan, atau tumpukan). Dalam arti file statis dan fungsi variabel statis adalah / bisa "global". Dalam konteks pertanyaan ini, agak jelas bahwa global mengacu pada lingkup bukan segmen alokasi (meskipun ada kemungkinan OP tidak mengetahui hal ini).
Paul A. Clayton
1
@ PaulA.Clayton Saya belum pernah mendengar istilah resmi yang disebut "segmen memori global". Variabel Anda akan berakhir di salah satu tempat berikut: register atau tumpukan (alokasi runtime), heap (alokasi runtime), segmen data. (Variabel penyimpanan statis diinisialisasi secara eksplisit), segmen .bss (variabel penyimpanan statis nol), .rodata (baca konstanta -hanya) atau .text (bagian dari kode). Jika mereka berakhir di tempat lain, itu adalah pengaturan khusus proyek.
Lundin
1
@ PaulA.Clayton Saya menduga apa yang Anda sebut sebagai "segmen global" adalah .datasegmennya.
Lundin