podejścia modułowe są ogólnie bardzo przydatne (przenośne i czyste), więc staram się programować moduły tak niezależnie, jak to możliwe. Większość moich podejść opiera się na strukturze opisującej sam moduł. Funkcja inicjalizacji ustawia podstawowe parametry, a następnie program obsługi (wskaźnik do struktury opisowej) jest przekazywany do dowolnej funkcji w module, która jest wywoływana.
W tej chwili zastanawiam się, jakie może być najlepsze podejście do alokacji pamięci dla struktury opisującej moduł. Jeśli to możliwe, chciałbym:
- Nieprzezroczysta struktura, więc strukturę można zmienić tylko przy użyciu dostarczonych funkcji interfejsu
- Wiele wystąpień
- pamięć przydzielona przez linker
Widzę następujące możliwości, że wszystkie są sprzeczne z jednym z moich celów:
globalna deklaracja
wiele instancji przypisanych przez linker, ale struct nie jest nieprzejrzysty
(#includes)
module_struct module;
void main(){
module_init(&module);
}
malloc
nieprzezroczysta struktura, wiele instancji, ale allocotion na stercie
w module.h:
typedef module_struct Module;
w module init moduł c, malloc i wskaźnik powrotu do przydzielonej pamięci
module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;
w main.c
(#includes)
Module *module;
void main(){
module = module_init();
}
deklaracja w module
nieprzezroczysta struktura, przydzielana przez linker, tylko z góry określona liczba instancji
trzymaj całą strukturę i pamięć wewnętrznie w module i nigdy nie ujawniaj programu obsługi ani struktury.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
Czy istnieje jakaś kombinacja tych opcji dla nieprzezroczystej struktury, linkera zamiast alokacji sterty i wielu / dowolnej liczby instancji?
rozwiązanie
jak zaproponowano w niektórych odpowiedziach poniżej, myślę, że najlepszym sposobem jest:
- zarezerwować miejsce na MODULE_MAX_INSTANCE_COUNT modułów w pliku źródłowym modułów
- nie definiuj MODULE_MAX_INSTANCE_COUNT w samym module
- dodaj #ifndef MODULE_MAX_INSTANCE_COUNT # błąd do pliku nagłówka modułów, aby upewnić się, że użytkownik modułów wie o tym ograniczeniu i określa maksymalną liczbę żądanych instancji dla aplikacji
- przy inicjalizacji instancji zwróć adres pamięci (* void) struktury opisowej lub indeks modułów (cokolwiek bardziej chcesz)
Odpowiedzi:
Pewnie że jest. Najpierw jednak zauważ, że „dowolna liczba” instancji musi zostać ustalona, lub przynajmniej ustalona górna granica, w czasie kompilacji. Jest to warunek wstępny statycznych alokacji instancji (tzw. „Alokacja linkera”). Możesz ustawić liczbę bez modyfikacji źródła, deklarując makro, które ją określa.
Następnie plik źródłowy zawierający rzeczywistą deklarację struktury i wszystkie powiązane z nią funkcje deklarują również tablicę instancji z wewnętrznym łączeniem. Zapewnia albo tablicę z zewnętrznym połączeniem wskaźników do instancji, albo funkcję dostępu do różnych wskaźników za pomocą indeksu. Wariant funkcji jest nieco bardziej modułowy:
moduł.c
Wydaje mi się, że znasz już sposób, w jaki nagłówek zadeklaruje strukturę jako niekompletny typ i zadeklaruje wszystkie funkcje (zapisane w kategoriach wskaźników do tego typu). Na przykład:
moduł.h
Teraz
struct module
jest nieprzezroczysty w jednostkach innych niż tłumaczeniemodule.c
, * i można uzyskać dostęp i używać do liczby przypadków określonych w czasie kompilacji bez dynamicznej alokacji.* Oczywiście, chyba że skopiujesz jego definicję. Chodzi o to, że
module.h
tego nie robi.źródło
Programuję małe mikrokontrolery w C ++, który osiąga dokładnie to, czego chcesz.
Moduł nazywany jest klasą C ++, może zawierać dane (dostępne zewnętrznie lub nie) i funkcje (podobnie). Konstruktor (funkcja dedykowana) inicjuje ją. Konstruktor może pobierać parametry czasu wykonywania lub parametry (moje ulubione) czasu kompilacji (szablon). Funkcje w klasie domyślnie uzyskują zmienną klasy jako pierwszy parametr. (Lub, często według moich preferencji, klasa może działać jako ukryty singleton, więc dostęp do wszystkich danych nie ma tego narzutu).
Obiekt klasy może być globalny (więc wiesz w czasie łączenia, że wszystko będzie pasować) lub lokalny stos, prawdopodobnie w głównym. (Nie lubię globałów C ++ z powodu nieokreślonej globalnej kolejności inicjalizacji, więc wolę lokalny stos).
Mój preferowany styl programowania polega na tym, że moduły są klasami statycznymi, a ich konfiguracja (statyczna) zależy od parametrów szablonu. Pozwala to uniknąć prawie wszystkich przewartościowań i umożliwia optymalizację. Połącz to z narzędziem, które oblicza rozmiar stosu i możesz spać bez obaw :)
Moje przemówienie na temat tego sposobu kodowania w C ++: Objects? Nie, dziękuję!
Wielu programistów osadzonych / mikrokontrolerów wydaje się nie lubić C ++, ponieważ uważają, że zmusiłoby ich to do użycia całego C ++. To absolutnie nie jest konieczne i byłby to bardzo zły pomysł. (Prawdopodobnie nie używasz również całego C! Pomyśl o stercie, zmiennoprzecinkowym, setjmp / longjmp, printf, ...)
W komentarzu Adam Haun wspomina o RAII i inicjalizacji. IMO RAII ma więcej wspólnego z dekonstrukcją, ale jego racja jest słuszna: obiekty globalne zostaną zbudowane przed twoimi głównymi startami, więc mogą działać na niepoprawnych założeniach (takich jak główna częstotliwość zegara, która zostanie później zmieniona). Jest to kolejny powód, dla którego NIE należy używać globalnych obiektów inicjowanych kodem. (Używam skryptu linkera, który zawiedzie, gdy będę miał globalne obiekty inicjowane kodem.) IMO takie „obiekty” powinny być jawnie tworzone i przekazywane. Obejmuje to obiekt „oczekiwania”, który udostępnia funkcję wait (). W moim ustawieniu jest to „obiekt”, który ustawia szybkość zegara układu.
Mówiąc o RAII: jest to jeszcze jedna funkcja C ++, która jest bardzo przydatna w małych systemach wbudowanych, chociaż nie z tego powodu (dezalokacja pamięci) jest najczęściej używana w dużych systemach (małe systemy wbudowane najczęściej nie używają dynamicznego zwalniania pamięci). Pomyśl o zablokowaniu zasobu: możesz uczynić zablokowany zasób obiektem opakowującym i ograniczyć dostęp do zasobu, aby był możliwy tylko za pomocą opakowującego blokowania. Gdy opakowanie opuści zakres, zasób zostaje odblokowany. Zapobiega to dostępowi bez blokowania i znacznie bardziej prawdopodobne jest, aby zapomnieć o odblokowaniu. z pewną magią (szablonową) może być zerowa.
Oryginalne pytanie nie wspominało o C, stąd moja C ++ - centryczna odpowiedź. Jeśli to naprawdę musi być C ....
Możesz użyć sztuczek z makrami: zadeklaruj publicznie swoje kanały, aby miały one typ i mogły być przydzielane globalnie, ale zmieniają nazwy swoich komponentów poza użyteczność, chyba że niektóre makro jest zdefiniowane inaczej, co ma miejsce w pliku .c modułu. Dla dodatkowego bezpieczeństwa możesz użyć czasu kompilacji w manipulacji.
Możesz też mieć publiczną wersję swojej struktury, która nie ma w niej nic użytecznego, i mieć wersję prywatną (z przydatnymi danymi) tylko w pliku .c i twierdzić, że mają ten sam rozmiar. Trochę sztuczek tworzenia plików może to zautomatyzować.
Komentarz @Lundins na temat złych (osadzonych) programistów:
Opisany typ programisty prawdopodobnie spowodowałby bałagan w dowolnym języku. Makra (obecne w C i C ++) są jednym oczywistym sposobem.
Oprzyrządowanie może w pewnym stopniu pomóc. Dla moich uczniów zlecam wbudowany skrypt, który określa brak wyjątków, no-rtti i daje błąd linkera, gdy używana jest sterty lub występują globały inicjowane kodem. Określa ostrzeżenie = błąd i włącza prawie wszystkie ostrzeżenia.
Zachęcam do używania szablonów, ale przy constexpr i pojęciach metaprogramowanie jest coraz mniej potrzebne.
„zdezorientowani programiści Arduino” Bardzo chciałbym zastąpić styl programowania Arduino (okablowanie, replikacja kodu w bibliotekach) nowoczesnym podejściem do C ++, które może być łatwiejsze, bezpieczniejsze i tworzyć szybszy i mniejszy kod. Gdybym tylko miał czas i moc ...
źródło
I believe FreeRTOS (maybe another OS?) does something like what you're looking for by defining 2 different versions of the struct.
The 'real' one, used internally by the OS functions, and a 'fake' one which is the same size as the 'real' one, but doesn't have any useful members inside (just a bunch of
int dummy1
and similar).Only the 'fake' struct is exposed outside of the OS code, and this is used to allocate memory to static instances of the struct.
Internally, when functions in the OS are called, they are passed the address of the external 'fake' struct as a handle, and this is then typecast as a pointer to a 'real' struct so the OS functions can do what they need to do.
źródło
In my opinion, this is pointless. You can put a comment there, but no point trying to hide it further.
C will never provide such high isolation, even if there is no declaration for the struct, it will be easy to accidentally overwrite it with e.g. mistaken memcpy() or buffer overflow.
Instead, just give the struct a name and trust other people to write good code also. It will also make debugging easier when the struct has a name you can use to refer to it.
źródło
Pure SW questions are better asked at /programming/.
The concept with exposing a struct of incomplete type to caller, like you describe, is often called "opaque type" or "opaque pointers" - anonymous struct means something else entirely.
The problem with this is that the caller won't be able to allocate instances of the object, only pointers to it. On a PC you would use
malloc
inside the objects "constructor", but malloc is a no-go in embedded systems.So what you do in embedded is to provide a memory pool. You have a limited amount of RAM, so restricting the number of objects that can be created is usually not a problem.
See Static allocation of opaque data types over at SO.
źródło