C modüler ürün yazılımı tasarımı için bellek ayırma olanakları


16

modüler yaklaşımlar genel olarak oldukça kullanışlı (taşınabilir ve temiz), bu yüzden modülleri mümkün olduğunca diğer modüllerden bağımsız olarak programlamaya çalışıyorum. Çoğu yaklaşım modülün kendisini tanımlayan bir yapıya dayanır. Bir başlatma işlevi birincil parametreleri ayarlar, daha sonra modül içindeki herhangi bir işleve bir işleyici (tanımlayıcı yapıya işaretçi) aktarılır.

Şu anda, bir modülü tanımlayan yapı için en iyi bellek ayırma yaklaşımının ne olabileceğini merak ediyorum. Mümkünse aşağıdakileri istiyorum:

  • Opak yapı, bu nedenle yapı yalnızca sağlanan arayüz işlevlerinin kullanılmasıyla değiştirilebilir.
  • Birden fazla örnek
  • bağlayıcı tarafından ayrılan bellek

Hedeflerimden biriyle çelişen şu olasılıkları görüyorum:

küresel deklarasyon

bağlayıcı tarafından kodlanan birden çok örnek, ancak yapı opak değil

(#includes)
module_struct module;

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

malloc

opak yapı, çoklu örnekler, ancak yığın üzerindeki allcotion

module.h dosyasında:

typedef module_struct Module;

module.c init işlevinde, malloc ve pointer için ayrılmış belleğe dön

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

main.c'de

(#includes)
Module *module;

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

modülde bildirim

opak yapı, bağlayıcı tarafından tahsis edildi, sadece önceden tanımlanmış sayıda örnek

tüm yapıyı ve hafızayı modülün içinde saklayın ve asla bir işleyici veya yapı göstermeyin.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

Bunları bir şekilde opak yapı, yığın tahsisi yerine bağlayıcı ve çoklu / herhangi bir sayıda örnek için birleştirme seçeneği var mı?

çözüm

aşağıdaki bazı cevaplarda önerildiği gibi, bence en iyi yol:

  1. modüller kaynak dosyasındaki MODULE_MAX_INSTANCE_COUNT modül için yer ayırın
  2. Modülde kendisinde MODULE_MAX_INSTANCE_COUNT tanımlamayın
  3. modüller kullanıcısının bu sınırlamanın farkında olduğundan ve uygulama için istenen maksimum örnek sayısını tanımladığından emin olmak için modüller başlık dosyasına bir #ifndef MODULE_MAX_INSTANCE_COUNT #error ekleyin
  4. bir örneğin başlatılması üzerine, açıklayıcı yapının bellek adresini (* geçersiz) veya modüller dizinini (daha fazla ne isterseniz) döndürün

12
Çoğu gömülü FW tasarımcısı, bellek kullanımını belirleyici ve basit tutmak için dinamik ayırmadan kaçınır. Özellikle çıplak metal ise ve hafızayı yönetmek için temel işletim sistemi yoksa.
Eugene Sh.

Tam olarak, bu yüzden linker tahsisleri yapmak istiyorum.
L. Heinrichs

4
Anladığımdan tam olarak emin değilim ... Dinamik sayıda örneğiniz varsa bağlayıcı tarafından ayrılan belleği nasıl alabilirdiniz? Bu benim için oldukça dik görünüyor.
jcaron

Neden bağlayıcının büyük bir bellek havuzu tahsis etmesine ve kendi tahsislerinizi yapmasına izin vermiyorsunuz ki bu da size sıfır havai bir ayırıcıdan fayda sağlıyor. Havuz nesnesini, özel olması için ayırma işleviyle dosyaya statik hale getirebilirsiniz. Kodumun bazılarında, çeşitli başlangıç ​​yordamlarındaki tüm ayırmaları yapıyorum, daha sonra ne kadar tahsis edildiğini yazdırıyorum, bu yüzden son üretim derlemesinde havuzu tam olarak bu boyuta ayarladım.
Lee Daniel Crocker

2
Derleme zamanı kararı ise, Makefile veya eşdeğerinizdeki sayıyı tanımlayabilirsiniz ve hepsi hazırdır. Sayı, modülün kaynağında değil, uygulamaya özgüdür ve parametre olarak yalnızca bir örnek numarası kullanırsınız.
jcaron

Yanıtlar:


4

Bunları bir şekilde anonim yapı, yığın tahsisi yerine bağlayıcı ve çoklu / herhangi bir sayıda örnek için birleştirme seçeneği var mı?

Elbette var. Ancak, önce, "herhangi bir sayıda" vakanın derleme zamanında sabitlenmesi veya en azından bir üst sınırın oluşturulması gerektiğini kabul edin. Bu, statik olarak tahsis edilecek örneklerin ("bağlayıcı ayırma" olarak adlandırdığınız) bir ön koşuldur. Bunu belirten bir makro bildirerek numarayı kaynak değişikliği olmadan ayarlanabilir yapabilirsiniz.

Ardından, gerçek yapı bildirimini ve tüm ilişkili işlevlerini içeren kaynak dosya, iç bağlantıya sahip bir dizi örneği de bildirir. Örneklere işaret eden harici bağlantıya sahip bir dizi veya başka çeşitli işaretleyicilere dizin yoluyla erişmek için bir işlev sağlar. Fonksiyon varyasyonu biraz daha modülerdir:

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];
}

Sanırım üstbilginin yapıyı tamamlanmamış bir tür olarak nasıl tanımlayacağını ve tüm işlevleri ( bu türe işaretçiler olarak yazılmış) bildireceğini zaten biliyorsunuzdur . Örneğin:

module.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Şimdi struct moduledışındaki çeviri birimlerinde opak module.c, * eriştiğiniz ve herhangi bir dinamik tahsisi olmadan derleme zamanında tanımlanan örnekleri sayısı kadar kullanabilirsiniz.


* Tabii ki tanımını kopyalamazsanız. Mesele şu ki, bunu module.hyapmaz.


İndeksi sınıfın dışından geçirmek garip bir tasarım. Bunun gibi bellek havuzları uyguladığımda, dizinin özel bir sayaç olmasına izin verdim, ayrılan her örnek için 1 artar. Yapıcı bellek yetersiz hatası döndürecek "NUM_MODULE_INSTANCES" e ulaşıncaya kadar.
Lundin

Bu adil bir nokta, @Lundin. Tasarımın bu yönü, endekslerin aslında önemli olan veya olmayan doğal bir öneme sahip olduğunu varsayar. Bu ise OP'ın başlayan dava için, trivially yüzden olsa durum. Bu önemi, eğer varsa, bundan başka, bir, örnek işaretçisi elde etmek için bir araç temin tarafından desteklenebilir olmaksızın başlatılması.
John Bollinger

Temel olarak n modül için bellek ayırırsınız, ne kadar kullanılırsa kullanılsın, ardından uygulama başlatılırsa bir sonraki kullanılmayan öğeye bir işaretçi döndürün. Sanırım bu işe yarayabilir.
L. Heinrichs

@ L.Heinrichs Evet, çünkü gömülü sistemler belirleyici niteliktedir. "Sonsuz miktarda nesne" veya "bilinmeyen miktarda nesne" diye bir şey yoktur. Nesneler de genellikle tek tonludur (donanım sürücüleri), bu nedenle nesnenin yalnızca tek bir örneği olacağından bellek havuzuna gerek yoktur.
Lundin

Çoğu dava için katılıyorum. Sorunun bazı teorik ilgileri de vardı. Ancak, yeterli IO'lar varsa yüzlerce 1 telli sıcaklık sensörü kullanabilirim (bir örnek olarak şu an ortaya koyabileceğim).
L. Heinrichs

22

Tam olarak istediğinizi sağlayan C ++ 'da küçük mikro denetleyiciler programlıyorum.

Bir modülü çağırdığınız şey bir C ++ sınıfıdır, veri (harici olarak erişilebilir ya da değil) ve işlevleri (aynı şekilde) içerebilir. Yapıcı (özel bir işlev) onu başlatır. Yapıcı çalışma zamanı parametrelerini veya (en sevdiğim) derleme zamanı (şablon) parametrelerini alabilir. Sınıf içindeki işlevler, birinci değişkeni olarak sınıf değişkenini dolaylı olarak alır. (Ya da, genellikle benim tercihim olarak, sınıf gizli bir singleton gibi davranabilir, bu nedenle tüm verilere bu yük olmadan erişilir).

Sınıf nesnesi küresel olabilir (bu nedenle bağlantı zamanında her şeyin sığacağını bilirsiniz) veya muhtemelen yerel olarak yığın yerel olabilir. (Tanımlanmamış genel başlatma sırası nedeniyle C ++ globalleri sevmiyorum, bu yüzden yığın yerel tercih ederim).

Tercih ettiğim programlama tarzı, modüllerin statik sınıflar olması ve (statik) yapılandırmasının şablon parametreleri tarafından olmasıdır. Bu neredeyse tüm kullanımdan kaçınır ve optimizasyonu sağlar. Bunu yığın boyutunu hesaplayan bir araçla birleştirin ve endişelenmeden uyuyabilirsiniz :)

C ++: Nesneler kodlama bu şekilde benim konuşma ? Hayır teşekkürler!

Bir çok gömülü / mikrodenetleyici programcısı C ++ 'dan hoşlanmıyor gibi görünüyor, çünkü onları tüm C ++' ı kullanmaya zorlayacağını düşünüyorlar . Bu kesinlikle gerekli değildir ve çok kötü bir fikir olacaktır. (Muhtemelen C'nin tamamını kullanmıyorsunuz! Öbek, kayan nokta, setjmp / longjmp, printf, ...)


Adam Haun bir yorumda RAII ve başlangıçtan bahsediyor. IMO RAII'nin yapısökümle ilgisi vardır, ancak onun amacı geçerlidir: küresel nesneler ana başlangıcınızdan önce inşa edilecek, bu nedenle geçersiz varsayımlar üzerinde çalışabilirler (lateron değiştirilecek bir ana saat hızı gibi). Bu, genel kodla başlatılan nesneleri kullanmamanın bir nedenidir. (Genel kod başlatılan nesneler olduğunda başarısız olacak bir bağlayıcı komut dosyası kullanın.) IMO gibi 'nesneler' açıkça oluşturulmalı ve iletilmelidir. Bu, wait () işlevi sağlayan bir 'bekleme' tesis 'nesnesi' içerir. Benim kurulumumda bu çipin saat hızını ayarlayan 'nesne'.

RAII hakkında konuşmak: Bu daha büyük sistemlerde en çok kullanılan nedeni (bellek yerleştirme) olmasa da (küçük gömülü sistemler çoğunlukla dinamik bellek yerleştirme kullanmaz) küçük gömülü sistemlerde çok yararlı bir C + + özelliğidir. Bir kaynağı kilitlemeyi düşünün: kilitli kaynağı bir sarıcı nesnesi haline getirebilir ve kaynağa erişimi yalnızca kilitleme sarıcısı aracılığıyla kısıtlayabilirsiniz. Sargı kapsam dışına çıktığında, kaynağın kilidi açılır. Bu, kilitlemeden erişimi önler ve kilidi açmayı unutmayı çok daha olası hale getirir. bazı (şablon) büyü ile sıfır ek yükü olabilir.


Orijinal soru C'den bahsetmedi, bu yüzden C ++ merkezli cevabım. Gerçekten C. olmalıdır.

Makro hileyi kullanabilirsiniz: donanımlarınızı herkese açık olarak bildirin, böylece bir türleri vardır ve küresel olarak tahsis edilebilir, ancak bazı makrolar farklı tanımlanmadığı sürece, modülünüzün .c dosyasında olduğu gibi, bileşenlerinin adlarını kullanılabilirliğin ötesinde kullanın. Ekstra güvenlik için manglamada derleme süresini kullanabilirsiniz.

Ya da, yapınızın hiçbir şeyi yararlı olmayan herkese açık bir sürümüne sahip olun ve özel sürümü (yararlı verilerle) yalnızca .c dosyanızda bulundurun ve aynı boyutta olduklarını iddia edin. Biraz dosya yapma hilesi bunu otomatikleştirebilir.


@Lundins, kötü (gömülü) programcılar hakkında yorum yapar:

  • Açıkladığınız programcı türü muhtemelen herhangi bir dilde karışıklık yaratacaktır. Makrolar (C ve C ++ 'da mevcuttur) bariz bir yoldur.

  • Takım, bir dereceye kadar yardımcı olabilir. Öğrencilerim için, istisna yok, no-rtti belirten ve yığın kullanıldığında ya da kodla başlatılan genel değerler bulunduğunda bir bağlayıcı hatası veren yerleşik bir komut dosyası zorunluyorum. Uyarı = hata belirtir ve neredeyse tüm uyarıları etkinleştirir.

  • Şablonları kullanmayı teşvik ediyorum, ancak constexpr ve kavramlarla metaprogramlama daha az gerekli.

  • "karışık Arduino programcıları" Ben çok Arduino (kablolama, kod kütüphanelerde çoğaltma) programlama tarzını daha kolay, daha güvenli ve daha hızlı ve daha küçük kod üretebilen modern bir C ++ yaklaşımıyla değiştirmek istiyorum. Keşke zamanım ve gücüm olsaydı ...


Bu cevap için teşekkürler! C ++ kullanmak bir seçenektir, ancak şirketimizde C kullanıyoruz (açıkça bahsetmediğim). İnsanları C. hakkında konuştuğumu bildirmek için soruyu güncelledim.
L. Heinrichs

Neden sadece C kullanıyorsunuz? Belki de bu onları en azından C ++ 'ı düşünmeye ikna etme şansı verir ... İstediğiniz şey C ++' da gerçekleşen (küçük bir kısmı) C ++ 'dır.
Wouter van Ooijen

(İlk 'gerçek' gömülü hobi projemde) yaptığım şey yapıcıda basit varsayılanı başlatmak ve ilgili sınıflar için ayrı bir Init yöntemi kullanmak. Başka bir avantajı, birim testi için saplama işaretçileri geçebilmemdir.
Michel Keijzers

2
@Moel bir hobi projesi için dili seçmekte özgür müsünüz? C ++ alın!
Wouter van Ooijen

4
O gömülü iyi C ++ programlar yazmak için gerçekten mükemmel mümkündür ederken, sorunun bazı olduğunu> tüm gömülü sistemler programcılar% 50 şarlatanlar, şaşkın PC programcılar, Arduino hobi vs vs insanlar bu tür basit vardır dışarı olamaz bir kullanmak yüzlerine açıklasanız bile temiz C ++ alt kümesi. Onlara C ++ verin ve bunu bilmeden önce, tüm STL, şablon meta programlaması, istisna işleme, çoklu kalıtım vb. Ve sonuç elbette tam bir çöp. Ne yazık ki, 10 gömülü C ++ projesinden 8'inin bu şekilde sonuçlandığı görülüyor.
Lundin

7

Ben inanıyorum FreeRTOS (belki başka bir OS?) Yapının 2 farklı sürümünü tanımlayarak aradığınız gibi bir şey yapıyor.
İşletim sistemi işlevleri tarafından dahili olarak kullanılan 'gerçek' ve 'gerçek' ile aynı boyutta olan, ancak içinde yararlı üyelere sahip olmayan (sadece bir grup int dummy1ve benzeri) bir 'sahte' olan .
OS kodunun dışında yalnızca 'sahte' yapı ortaya çıkar ve bu, yapının statik örneklerine bellek ayırmak için kullanılır.
Dahili olarak, işletim sistemindeki işlevler çağrıldığında, dış 'sahte' yapının adresini bir tanıtıcı olarak geçirirler ve bu daha sonra işletim sistemi işlevlerinin ihtiyaç duydukları şeyi yapabilmesi için 'gerçek' bir yapıya bir işaretçi olarak yazılır. yapmak.


İyi fikir, sanırım --- #define BUILD_BUG_ON (koşul) ((geçersiz) sizeof (char [1 - 2 * !! (koşul)])) --- BUILD_BUG_ON (sizeof (real_struct)! = Sizeof ( fake_struct)) ----
L. Heinrichs

2

Anonim yapı, bu nedenle yapı yalnızca sağlanan arayüz işlevlerinin kullanımı ile değiştirilebilir.

Bence bu anlamsız. Buraya bir yorum yazabilirsiniz, ancak daha fazla saklamaya çalışmanın bir anlamı yoktur.

C, yapı için herhangi bir bildirim olmasa bile, asla böyle yüksek bir izolasyon sağlamaz, yanlışlıkla yanlışlıkla memcpy () veya tampon taşması ile üzerine yazmak kolay olacaktır.

Bunun yerine, yapıya bir ad verin ve iyi kod yazmaları için diğer insanlara güvenin. Ayrıca, yapının başvurmak için kullanabileceğiniz bir adı olduğunda hata ayıklamayı kolaylaştırır.


2

Saf SW soruları /programming/ adresinde daha iyi sorulur .

Açıkladığınız gibi, eksik tipteki bir yapıyı arayana açıklayan kavrama genellikle "opak tip" veya "opak işaretçiler" denir - anonim yapı tamamen başka bir şey anlamına gelir.

Buradaki problem, arayanın nesnenin örneklerini tahsis edemeyeceğidir, sadece ona işaret eder. Bir bilgisayarda malloc"yapıcı" nesnelerinin içinde kullanırsınız , ancak malloc gömülü sistemlerde kullanılmaz.

Yani gömülü olarak yaptığınız şey bir bellek havuzu sağlamaktır. Sınırlı miktarda RAM'iniz olduğundan, oluşturulabilecek nesne sayısını kısıtlamak genellikle sorun değil.

Bkz . SO'da opak veri türlerinin statik olarak atanması .


Sonunda isimlendirme karışıklığını netleştirdiğiniz için teşekkür ederim, OP'yi ayarlayacağım. Taşmayı istiflemeyi düşünüyordum, ancak özellikle gömülü programcıları hedeflemek istediğime karar verdim.
L. Heinrichs
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.