Kemungkinan untuk mengalokasikan memori untuk desain firmware modular di C

16

Pendekatan modular pada umumnya cukup praktis (portabel dan bersih), jadi saya mencoba memprogram modul yang terpisah dari modul lainnya. Sebagian besar pendekatan saya didasarkan pada sebuah struct yang menggambarkan modul itu sendiri. Fungsi inisialisasi menetapkan parameter utama, kemudian pawang (pointer ke struct deskriptif) dilewatkan ke fungsi apa pun di dalam modul yang disebut.

Saat ini, saya bertanya-tanya apa pendekatan terbaik alokasi memori untuk struct yang menggambarkan modul. Jika memungkinkan, saya ingin yang berikut ini:

  • Struct buram, sehingga struct hanya dapat diubah oleh penggunaan fungsi antarmuka yang disediakan
  • Banyak contoh
  • memori yang dialokasikan oleh tautan

Saya melihat kemungkinan berikut, bahwa semua bertentangan dengan salah satu tujuan saya:

deklarasi global

banyak contoh, dialihkan oleh tautan, tetapi struct tidak buram

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

malloc

struct buram, beberapa contoh, tetapi semua ramuan di tumpukan

dalam module.h:

typedef module_struct Module;

dalam fungsi module.c init, malloc dan mengembalikan pointer ke memori yang dialokasikan

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

di main.c

(#includes)
Module *module;

void main(){
    module = module_init();
}

deklarasi dalam modul

struct buram, dialokasikan oleh linker, hanya jumlah instance yang telah ditentukan

menjaga seluruh struct dan memori internal ke modul dan tidak pernah mengekspos handler atau struct.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

Apakah ada opsi untuk menggabungkan ini entah bagaimana untuk struct buram, linker alih-alih alokasi tumpukan dan beberapa / jumlah instance?

larutan

seperti yang diusulkan dalam beberapa jawaban di bawah ini, saya pikir cara terbaik adalah:

  1. cadangan ruang untuk MODULE_MAX_INSTANCE_COUNT modul dalam file sumber modul
  2. jangan mendefinisikan MODULE_MAX_INSTANCE_COUNT dalam modul itu sendiri
  3. tambahkan #ifndef MODULE_MAX_INSTANCE_COUNT #error ke file header modul untuk memastikan pengguna modul mengetahui batasan ini dan menentukan jumlah instance maksimum yang diinginkan untuk aplikasi
  4. pada inisialisasi instance, kembalikan alamat memori (* void) dari struct desricptive atau indeks modul (apa pun yang Anda suka)
L. Heinrichs
sumber
12
Sebagian besar desainer FW tertanam menghindari alokasi dinamis untuk menjaga penggunaan memori tetap deterministik dan sederhana. Terutama jika itu bare-metal dan tidak memiliki OS yang mendasari untuk mengelola memori.
Eugene Sh.
Tepat, itulah sebabnya saya ingin tautan melakukan alokasi.
L. Heinrichs
4
Saya tidak yakin saya mengerti ... Bagaimana Anda dapat memiliki memori yang dialokasikan oleh linker jika Anda memiliki jumlah instance yang dinamis? Bagi saya itu agak ortogonal.
jcaron
Mengapa tidak membiarkan penghubung mengalokasikan satu kumpulan memori besar, dan melakukan alokasi Anda sendiri dari itu, yang juga memberi Anda manfaat dari pengalokasi nol-overhead. Anda dapat membuat objek kolam statis ke file dengan fungsi alokasi sehingga bersifat pribadi. Dalam beberapa kode saya, saya melakukan semua alokasi dalam berbagai rutinitas init, kemudian saya mencetak berapa banyak yang dialokasikan, jadi dalam kompilasi produksi akhir saya mengatur kumpulan ke ukuran yang tepat.
Lee Daniel Crocker
2
Jika ini adalah keputusan waktu kompilasi, Anda cukup menentukan nomor di Makefile atau yang setara, dan Anda siap. Nomor tidak akan di sumber modul, tetapi akan spesifik aplikasi, dan Anda hanya menggunakan nomor contoh sebagai parameter.
jcaron

Jawaban:

4

Apakah ada opsi untuk menggabungkan ini entah bagaimana untuk struct anonim, linker alih-alih alokasi tumpukan dan beberapa / jumlah instance?

Tentu ada. Namun, pertama-tama, ketahuilah bahwa "jumlah apa pun" instance harus diperbaiki, atau setidaknya batas atas yang ditetapkan, pada waktu kompilasi. Ini merupakan prasyarat agar instance dialokasikan secara statis (apa yang Anda sebut "alokasi tautan"). Anda dapat membuat angka dapat disesuaikan tanpa modifikasi sumber dengan mendeklarasikan makro yang menentukannya.

Kemudian file sumber yang berisi deklarasi struct aktual dan semua fungsi yang terkait juga mendeklarasikan array instance dengan tautan internal. Ini menyediakan array, dengan tautan eksternal, dari pointer ke instance atau fungsi untuk mengakses berbagai pointer berdasarkan indeks. Variasi fungsi sedikit lebih modular:

module.c

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

Saya kira Anda sudah terbiasa dengan bagaimana header akan mendeklarasikan struct sebagai tipe yang tidak lengkap dan mendeklarasikan semua fungsi (ditulis dalam bentuk pointer ke tipe itu). Sebagai contoh:

module.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Sekarang struct moduleopak di unit terjemahan selain module.c, * dan Anda dapat mengakses dan menggunakan hingga jumlah instance yang ditentukan pada waktu kompilasi tanpa alokasi dinamis apa pun.


* Kecuali Anda menyalin definisinya, tentu saja. Intinya adalah module.hjangan lakukan itu.

John Bollinger
sumber
Saya pikir ini adalah desain yang aneh untuk melewati indeks dari luar kelas. Ketika saya menerapkan kumpulan memori seperti ini, saya membiarkan indeks menjadi penghitung pribadi, meningkat sebesar 1 untuk setiap instance yang dialokasikan. Hingga Anda mencapai "NUM_MODULE_INSTANCES", di mana konstruktor akan mengembalikan kesalahan kehabisan memori.
Lundin
Itu poin yang adil, @Lundin. Aspek desain mengandaikan bahwa indeks memiliki signifikansi yang melekat, yang mungkin atau mungkin tidak terjadi. Ini adalah kasusnya, meskipun sepele, untuk kasus awal OP. Signifikansi seperti itu, jika ada, dapat lebih jauh didukung dengan menyediakan sarana untuk mendapatkan pointer contoh tanpa menginisialisasi.
John Bollinger
Jadi pada dasarnya Anda menyimpan memori untuk modul n, tidak peduli berapa banyak yang akan digunakan, kemudian mengembalikan pointer ke elemen yang tidak digunakan berikutnya jika aplikasi menginisialisasi itu. Saya kira itu bisa berhasil.
L. Heinrichs
@ L.Heinrichs Ya, karena embedded system bersifat deterministik. Tidak ada yang namanya "jumlah objek tanpa batas" atau "jumlah objek yang tidak diketahui". Objek sering juga lajang (driver perangkat keras), jadi seringkali tidak perlu untuk kumpulan memori, karena hanya satu contoh objek yang akan ada.
Lundin
Saya setuju untuk sebagian besar kasus. Pertanyaan itu juga memiliki minat teoretis. Tapi saya bisa menggunakan ratusan sensor suhu 1-kawat jika ada cukup IO yang tersedia (sebagai salah satu contoh saya dapat datang dengan sekarang).
L. Heinrichs
22

Saya memprogram pengendali mikro kecil di C ++, yang mencapai apa yang Anda inginkan.

Apa yang Anda sebut modul adalah kelas C ++, ia dapat berisi data (baik yang dapat diakses secara eksternal atau tidak) dan fungsinya (juga). Konstruktor (fungsi khusus) memulai itu. Konstruktor dapat mengambil parameter run-time atau (waktu tempuh) parameter (favorit saya) saya. Fungsi-fungsi dalam kelas secara implisit mendapatkan variabel kelas sebagai parameter pertama. (Atau, seringkali preferensi saya, kelas dapat bertindak sebagai singleton tersembunyi, sehingga semua data diakses tanpa overhead ini).

Objek kelas dapat bersifat global (sehingga Anda tahu pada saat tautan bahwa semuanya akan cocok), atau stack-local, mungkin di main. (Saya tidak suka C ++ global karena urutan inisialisasi global yang tidak ditentukan, jadi saya lebih suka stack-lokal).

Gaya pemrograman pilihan saya adalah bahwa modul adalah kelas statis, dan konfigurasi (statis) mereka adalah dengan parameter template. Ini menghindari hampir semua overhad dan memungkinkan optimasi. Kombinasikan ini dengan alat yang menghitung ukuran tumpukan dan Anda dapat tidur tanpa khawatir :)

Pembicaraan saya tentang cara pengkodean dalam C ++: Objects? Tidak, terima kasih!

Banyak programmer embedded / mikrokontroler tampaknya tidak menyukai C ++ karena mereka pikir itu akan memaksa mereka untuk menggunakan semua C ++. Itu sama sekali tidak perlu, dan akan menjadi ide yang sangat buruk. (Anda mungkin juga tidak menggunakan semua C! Pikirkan heap, floating point, setjmp / longjmp, printf, ...)


Dalam komentarnya Adam Haun menyebutkan RAII dan inisialisasi. IMO RAII lebih terkait dengan dekonstruksi, tetapi poinnya valid: objek global akan dibangun sebelum mulai utama Anda, sehingga mereka dapat bekerja pada asumsi yang tidak valid (seperti kecepatan clock utama yang akan diubah nanti). Itu adalah satu lagi alasan untuk TIDAK menggunakan objek yang diinisialisasi kode global. (Saya menggunakan skrip linker yang akan gagal ketika saya memiliki objek yang diinisialisasi kode global.) IMO 'objek' tersebut harus dibuat dan diteruskan secara eksplisit. Ini termasuk objek 'fasilitas menunggu' 'yang menyediakan fungsi wait (). Dalam pengaturan saya ini adalah 'objek' yang mengatur kecepatan clock chip.

Berbicara tentang RAII: itu adalah satu lagi fitur C ++ yang sangat berguna dalam sistem embedded kecil, meskipun bukan karena alasan (memory deallocation) yang paling banyak digunakan dalam sistem largere (sistem embedded kecil kebanyakan tidak menggunakan deallocation memory dinamis). Pikirkan untuk mengunci sumber daya: Anda dapat menjadikan sumber daya yang dikunci sebagai objek pembungkus, dan membatasi akses ke sumber daya hanya dimungkinkan melalui bungkus pengunci. Ketika pembungkus keluar dari ruang lingkup, sumber daya tidak dikunci. Ini mencegah akses tanpa mengunci, dan membuatnya lebih tidak mungkin untuk melupakan pembukaan kunci. dengan beberapa sihir (templat) itu bisa nol di atas kepala.


Pertanyaan aslinya tidak menyebutkan C, maka jawaban C ++ - centric saya. Jika benar-benar harus ....

Anda dapat menggunakan tipuan makro: nyatakan stucts Anda secara publik, sehingga mereka memiliki tipe dan dapat dialokasikan secara global, tetapi potong nama-nama komponen mereka di luar kegunaan, kecuali beberapa makro didefinisikan secara berbeda, yang merupakan kasus dalam file .c modul Anda. Untuk keamanan ekstra, Anda dapat menggunakan waktu kompilasi dalam mangling.

Atau miliki versi publik dari struct Anda yang tidak memiliki apa pun yang berguna di dalamnya, dan miliki versi pribadi (dengan data yang berguna) hanya di file .c Anda, dan nyatakan bahwa ukurannya sama. Sedikit tipuan file make bisa mengotomatiskan ini.


@Lundins berkomentar tentang programmer yang buruk (tertanam):

  • Jenis programmer yang Anda gambarkan mungkin akan membuat kekacauan dalam bahasa apa pun. Makro (hadir dalam C dan C ++) adalah salah satu cara yang jelas.

  • Tooling dapat membantu sampai batas tertentu. Untuk murid-murid saya, saya memberi mandat skrip dibangun yang menentukan tanpa-pengecualian, tidak-rtti, dan memberikan kesalahan linker ketika tumpukan digunakan atau global-inisialisasi kode hadir. Dan itu menentukan peringatan = kesalahan dan mengaktifkan hampir semua peringatan.

  • Saya menganjurkan menggunakan templat, tetapi dengan constappr dan konsep metaprogramming semakin dibutuhkan.

  • "programmer Arduino yang bingung" Saya sangat ingin mengganti gaya pemrograman Arduino (kabel, replikasi kode di perpustakaan) dengan pendekatan C ++ modern, yang dapat lebih mudah, lebih aman, dan menghasilkan kode yang lebih cepat dan lebih kecil. Kalau saja saya punya waktu dan kekuatan ....

Wouter van Ooijen
sumber
Terima kasih atas jawaban ini! Menggunakan C ++ adalah sebuah pilihan, tetapi kami menggunakan C di perusahaan saya (apa yang saya sebutkan secara eksplisit). Saya telah memperbarui pertanyaan untuk memberi tahu orang-orang bahwa saya berbicara tentang C.
L. Heinrichs
Mengapa Anda menggunakan (hanya) C? Mungkin ini memberi Anda kesempatan untuk meyakinkan mereka untuk setidaknya mempertimbangkan C ++ ... Apa yang Anda inginkan sangat penting (sebagian kecil) C ++ direalisasikan dalam C.
Wouter van Ooijen
Apa yang saya lakukan di proyek hobi tertanam 'nyata' pertama saya) adalah menginisialisasi default sederhana dalam konstruktor, dan menggunakan metode Init terpisah untuk kelas yang relevan. Manfaat lain adalah saya bisa melewati pointer rintisan untuk pengujian unit.
Michel Keijzers
2
@Michel untuk proyek hobi kamu bebas memilih bahasa? Ambil C ++!
Wouter van Ooijen
4
Dan walaupun memang sangat mungkin untuk menulis program C ++ yang baik untuk embedded, masalahnya adalah bahwa beberapa> 50% dari semua programmer sistem embedded di luar sana adalah dukun, programmer PC bingung, penggemar Arduino dll. Orang-orang seperti ini tidak dapat menggunakan bersihkan subset dari C ++, bahkan jika Anda menjelaskannya ke wajah mereka. Beri mereka C ++ dan sebelum Anda mengetahuinya, mereka akan menggunakan seluruh STL, metaprogramming template, penanganan pengecualian, pewarisan berganda dan sebagainya. Dan hasilnya tentu saja sampah lengkap. Sayangnya, ini adalah bagaimana 8 dari 10 proyek C ++ yang disematkan berakhir.
Lundin
7

Saya percaya FreeRTOS (mungkin OS lain?) Melakukan sesuatu seperti apa yang Anda cari dengan mendefinisikan 2 versi berbeda dari struct.
Yang 'asli', digunakan secara internal oleh fungsi OS, dan yang 'palsu' yang ukurannya sama dengan yang 'asli', tetapi tidak memiliki anggota yang berguna di dalamnya (hanya sekelompok int dummy1dan sejenisnya).
Hanya struct 'palsu' yang diekspos di luar kode OS, dan ini digunakan untuk mengalokasikan memori ke instance statis dari struct.
Secara internal, ketika fungsi-fungsi dalam OS dipanggil, mereka meneruskan alamat struct 'palsu' eksternal sebagai pegangan, dan ini kemudian typecast sebagai pointer ke struct 'nyata' sehingga fungsi-fungsi OS dapat melakukan apa yang mereka butuhkan untuk melakukan.

brhans
sumber
Ide bagus, saya kira saya bisa menggunakan --- #define BUILD_BUG_ON (kondisi) ((kosong) sizeof (char [1 - 2 * !! (kondisi)])) --- BUILD_BUG_ON (sizeof (real_struct)! = Sizeof ( fake_struct)) ----
L. Heinrichs
2

Struct Anonim, jadi struct hanya dapat diubah dengan menggunakan fungsi antarmuka yang disediakan

Menurut saya, ini tidak ada gunanya. Anda dapat memberikan komentar di sana, tetapi tidak ada gunanya mencoba menyembunyikannya lebih lanjut.

C tidak akan pernah memberikan isolasi setinggi itu, bahkan jika tidak ada deklarasi untuk struct, akan mudah untuk secara tidak sengaja menimpanya dengan mis miscancake memcpy () atau buffer overflow.

Sebagai gantinya, beri saja struct nama dan percaya orang lain untuk menulis kode yang baik juga. Ini juga akan membuat proses debug lebih mudah ketika struct memiliki nama yang dapat Anda gunakan untuk merujuknya.

jpa
sumber
2

Pertanyaan SW murni lebih baik ditanyakan di /programming/ .

Konsep dengan mengekspos sebuah struct tipe tidak lengkap untuk penelepon, seperti yang Anda jelaskan, sering disebut "tipe buram" atau "pointer buram" - struct anonim berarti sesuatu yang sama sekali berbeda.

Masalah dengan ini adalah bahwa penelepon tidak akan dapat mengalokasikan instance objek, hanya pointer ke sana. Pada PC Anda akan menggunakan mallocdi dalam objek "konstruktor", tetapi malloc adalah no-go dalam sistem embedded.

Jadi apa yang Anda lakukan di embedded adalah menyediakan kumpulan memori. Anda memiliki jumlah RAM yang terbatas, sehingga membatasi jumlah objek yang dapat dibuat biasanya tidak menjadi masalah.

Lihat alokasi statis tipe data buram ke arah di SO.

Lundin
sumber
Ou terima kasih telah menjelaskan kebingungan penamaan di pihak saya, sakit menyesuaikan OP. Saya berpikir untuk menumpuk overflow, tetapi memutuskan saya ingin menargetkan pemrogram tertanam secara khusus.
L. Heinrichs