logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Przykłady zastosowań unii i struktur w języku C (embedded)

mtbchn 23 Cze 2022 14:23 966 19
  • #1 20072775
    mtbchn
    Poziom 3  
    Cześć,
    korzystam regularnie ze struktur, tablic struktur i wskaźników na struktury. Wiem jaka jest różnica między strukturą i unią. Zastanawiam się jednak nad praktycznymi przykładami wykorzystania unii oraz połączenia unii ze strukturami. Czy dysponujecie jakimiś przykładami (embedded), gdzie połączenie unii ze strukturą pokazuje sens tego tandemu? Tak, żebym mógł przeanalizować, zrozumieć, zainspirować się i stosować ewentualnie w swoich projektach. Z góry dzięki za podpowiedź.
  • #2 20072821
    JacekCz
    Poziom 42  
    Zastosowania są bardzo rzadkie.

    a) forma konwersji "czegoś" na bajty / inty. Żeby dla celów "użytkowych" to była jakaś wzglednie złożona struktura, a dla kontrolera DMA tablica bajtów.
    b) rzeczywisty obiekt, który dość często w dialektach C++ nazywa się "Variant", zdolny do noszenia jednej wartości któregoś z wielu typów float, całkowitoliczbowych, string, data i co tam jeszcze. A nawet pewnie tego by nie było, gdyby wszystko w C++ dziedziczyło z nadrzędnego "Objectu"

    ... wiec bardzo rzadkie. Praktycznie wszystkie tricki są nieprzenośne (przestają działać na odmiennym CPU / innych opcjach kompilacji. A jeszcze z tego "rynku" są wypierane przez funkcje z argumentami void *, one wystarczają np do binarnego zapisu struktur.
    Inne języki tego nie mają, i nikt sobie żył z powodu tego braku nie podciął. Przyjmij, że nie musisz tego używać.

    Dodano po 4 [minuty]:

    Moze jedyne trudne do zastąpienia

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Ale znów, uprawiałem to 30 lat temu tylko dlatego, ze projekt był zaczęty na bazie innego, kontynuacja.
    Czy zmienne bitowe to takie szczęście ... na pewno to ułomne zmienne.

    Widziałem takie unie dla odwzorowanie rejestru flag z CPU.
  • #3 20072830
    stachu_l
    Poziom 37  
    Często stosowane w dostępie do rejestrów gdzie są oddzielne pola bitowe czy bajtowe o różnych funkcjach.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Generalnie wszystkie przypadki gdzie dany rejon dostępujesz w różny sposób.
  • #4 20072837
    JacekCz
    Poziom 42  
    stachu_l napisał:
    Generalnie wszystkie przypadki gdzie dany rejon dostępujesz w różny sposób.


    ... ważniejsze pytanie nie "jak" ale "dlaczego". Dlaczego miałbym potrzebować takiego dostępu

    Dodano po 33 [sekundy]:

    stachu_l napisał:
    Często stosowane


    ?
  • #5 20072856
    stachu_l
    Poziom 37  
    JacekCz napisał:
    Dlaczego miałbym potrzebować takiego dostępu
    Dala, w miarę eleganckiego, dostępu do pól rejestru. Oczywiście można jawnie robić maskowanie bitów pola i przesunięcia albo napisać makra dla każdego pola ale w czym to jest lepsze od pól bitowych? Czy jest czytelniejsze?
  • #6 20073207
    khoam
    Poziom 42  
    stachu_l napisał:
    Dala, w miarę eleganckiego, dostępu do pól rejestru. Oczywiście można jawnie robić maskowanie bitów pola i przesunięcia albo napisać makra dla każdego pola ale w czym to jest lepsze od pól bitowych? Czy jest czytelniejsze?

    Zwykle i tak występują dodatkowo makra - nie trzeba wtedy pamiętać nazw poszczególnych pól unii.
  • #7 20073222
    stachu_l
    Poziom 37  
    khoam napisał:
    nie trzeba wtedy pamiętać nazw poszczególnych pól unii.
    Wystarczy pamiętać nazwy makr :-).
    Jak masz wszystkie rejestry opisane w tym stylu (value, bits.) a pola bitowe odpowiadają nazwom zapisanym w dokumentacji HW rejestrów to też wiele nie trzeba pamiętać poza tym opisem HW rejestrów.
    Pola bitowe i odpowiednie marka są alternatywą i chyba zależnie od projektu czy zespołu w którym tworzymy oprogramowanie przewagę mają jedne albo drugie - troche jak dyskusja "o wyższości świąt Bożego Narodzenia nad Wielkanocy".
  • #8 20073230
    tmf
    VIP Zasłużony dla elektroda
    Moim zdaniem połączenie typu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    i potem użycie compound literal, np.:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    jest po prostu czytelniejsze i bardziej intuicyjne, niż ręczne przesunięcia bitowe, poza tym zmniejsza ryzyko pomyłki. Operator << łatwo pomylić z < i potem można być zaskoczonym wynikiem.
    Biorąc pod uwagę automatyczne podpowiedzi IDE, pamiętanie pól nie jest problemem. Zresztą alternatywą jest pamiętanie nazw definicji, więc wyjdzie na jedno.
    Połączenie struktury z unią przydaje się często w różnych operacjach graficznych, gdzie trzeba pakować i rozpakowywać dane o kolorach. Można to zrobić ręcznie, za pomoca przesunięć i masek, ale wolę to przerzucić na kompilator, niech się męczy :)
  • #9 20073231
    JacekCz
    Poziom 42  
    stachu_l napisał:
    Dala, w miarę eleganckiego, dostępu do pól rejestru.


    jedyne, które zachowało mi się w głowie jako w miarę eleganckie, to rejestr flag CPU.
    Moze zostać pushowany na stos, staje sie takim samym słowem jak inne. Gdyby na stosie przyszło "spreparować" rejestr (po co ??? jakiś bardzo dziwny longjmp ???)

    BTW ktoś potrzebuje analizować flagi CPU wyniesione na stos? Tylko nie tłoczyć się, po kolei ....

    Tu unia mentalnie odwzorowuje to, co się dzieje .
    Nie przypominam sobie żadnego innego, abym okresił jako elegancie.

    Maski bitowe, raz przygotowane, są bardziej bezpieczne pod względem np zmiany parametrów kompilacji, alligmentu itd ...
  • #10 20073233
    khoam
    Poziom 42  
    @stachu_l Stosowanie makr ma w tym wypadku tę zaletę, że można "ukryć" w ich definicji jakąś formę walidacji przekazywanych wartości. Chociażby przez asercje, które będą wyłączane w finalnej wersji programu.
  • #11 20073278
    stachu_l
    Poziom 37  
    JacekCz napisał:
    jedyne, które zachowało mi się w głowie jako w miarę eleganckie, to rejestr flag CPU.
    Ale ja mówię o rejestrach HW podłączonego do procesora - kontroler UART, USB, SPI itp.

    khoam napisał:
    Stosowanie makr ma w tym wypadku tę zaletę, że można "ukryć" w ich definicji jakąś formę walidacji przekazywanych wartości.
    Tylko makra przy dostępie muszą być dwa - do zapisu i do odczytu wartości jeżeli bity pola mogą być modyfikowane przez obie strony (CPU i HW).
    Osobiście wolę czysta definicję pola bitowego a kontrole wartości w funkcji dostępu używającej tego pola bitowego a nie ukrytą w makro. Ale to jest zagadnienie typu: "de gustibus non est disputandum" więc nie przekonujmy się które lepsze.
    Autor pyta do czego stosuje się w embedded i oczywiście można zaproponować alternatywę makr ale to jest ostatecznie jego decyzja czy zastosuje takie unie.
  • #12 20073799
    JacekCz
    Poziom 42  
    stachu_l napisał:
    Ale ja mówię o rejestrach HW podłączonego do procesora - kontroler UART, USB, SPI itp.


    Czy one kiedykolwiek są "dualne" tzn raz słowowe, raz bitowe ? Wtedy użyłbym swojego słówka o elegancji.
    Może tak, może nie, nie napisałeś chyba o architekturze.

    PRZYPUSZCZAM, ale myślę już bardziej C++ niż C, porty urządzenia bym je enkapsulował funkcjonalnie, jako metody. Pola (np bitowe) to starsznie, strasznie mało, to TYLKO dane z którym nadal trzeba coś mądrze robić (i nie spieprzyć - aktywny kod to o wiele lepiej)

    MSZ szkoda energii na uczenie się unii na sposób C (jako świadomy etap edukacji, wytyczony na sztandarach) - raczej oswój podstawy C++, lepsza inwestycja.
  • #13 20073994
    rb401
    Poziom 39  
    mtbchn napisał:
    Zastanawiam się jednak nad praktycznymi przykładami wykorzystania unii oraz połączenia unii ze strukturami. Czy dysponujecie jakimiś przykładami (embedded), gdzie połączenie unii ze strukturą pokazuje sens tego tandemu?


    Atrakcyjnym zastosowaniem mieszania struktur i unii w jednym typie, są przykładowo systemy gdzie transmituje się dane w pakietach o stałej, w miarę małej długości, ale informacja różni się jednak pewnymi składnikami a stosowanie rozwiązań typu np. JSON nie wchodzi w grę z różnych przyczyn. Do tego chcemy bardzo prostego, wygodnego dostępu do składowych takiego pakietu w buforze pamięci i też łatwego zapisu i odczytu takich, wymieszanych w kwestii typów czujników, pakietów z pliku. Szczególnie może być to przydatne w embedded.

    Może prosty przykład praktyczny. Powiedzmy że mamy sieć czujników różnych wielkości fizycznych, gdzie oprócz przesyłu specyficznych dla typu czujnika mierzonych wartości liczbowych o różnym typie i długości, mamy też wspólne dla wszystkich posiadanych czujników standardowe informacje typu numer fabryczny czujnika, typ czujnika, napięcie baterii, status itd. .
    Wtedy definiujemy strukturę gdzie składowymi będą te wspólne składniki, w tym unia, najlepiej unia anonimowa, na te specyficzne wartości liczbowe pomiarów. Czyli np. termometr ma przykładowo temperaturę float, enkoder położenie na int, magnetometr XYZ, powiedzmy trzy float itp. .
    Od strony pisania programu ogromnie się upraszcza dostęp do zmiennej takiego typu, bo np. odczytując pole typu czujnika i stwierdzając że to termometr przysłał, odwołujemy się prosto do pola temperatura. Jeśli inny typ to czytamy specyficzne dla tego typu pola, używając tylko innej nazwy. Żadnego rzutowania typów czy zabawy z offsetami w takim podejściu nie trzeba.
  • #14 20074315
    JacekCz
    Poziom 42  
    rb401 napisał:
    Żadnego rzutowania typów czy zabawy z offsetami w takim podejściu nie trzeba.


    ale znacznej uwagi tak, bo wspomagania od kompilatora zero.

    Unia może "odżyć" w C++ dla danych wariantowych, i de facto tam żyje - choć w sposób niewidoczny dla użytkownika klasy.
  • #15 20074394
    _lazor_
    Moderator Projektowanie
    JacekCz napisał:
    ale znacznej uwagi tak, bo wspomagania od kompilatora zero.


    Dlaczego uważasz że nie ma "wspomagania" ze strony kompilatora? Brzmi to niedorzecznie.
  • #16 20074398
    Marek_Skalski
    VIP Zasłużony dla elektroda
    JacekCz napisał:
    Czy one kiedykolwiek są "dualne" tzn raz słowowe, raz bitowe ?
    Owszem, są takie architektury, w których jest wymagany specyficzny dostęp do rejestrów. Przykładem może być moduł SPI w nowszych STM32. Dane do transmisji zapisuje się do rejestru danych, który może mieć 8 lub 16 bitów szerokości. Operacja zapisu wyzwala przepisanie zawartości rejestru danych (DR) do FIFO o określonej głębokości, np. 16 poziomów, a dopiero z FIFO następuje transmisja w świat. Skutkiem zapisu bajtu z domyślną szerokością szyny (16-bitów) jest zapisanie dwóch bajtów do FIFO zamiast jednego, przy czym ten drugi jest błędny, zbędny i często przypadkowy. Podobnie wygląda sprawa z odczytem danych, tylko tutaj łatwo zgubić dane odczytując FIFO. Można się złościć, że tak to producent układu wymyślił, ale do tej pory nie pojawił się osobny rejestr 8-bitowy dla transmisji danych o długości nie przekraczającej ośmiu bitów. Można użyć rzutowania, a można użyć unii. Wybór należy do programisty. Można znaleźć więcej przykładów, ale starałem się tylko odpowiedzieć na Twoje pytanie i podsunąć trop Autorowi tematu.
  • #17 20074694
    jvoytech
    Poziom 21  
    Niektóre biblioteki deklarowały strukturę, wewnątrz której była unia z wariantami danych i jedno pole wskazujące, która z nich jest aktywna. Można było w prosty sposób stworzyć tablicę, przechowującą obiekty z różnymi typami jednocześnie.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Unie często widuję w bibliotekach graficznych pozwalającą na dostęp do wektora 3d jako pozycja "xyz", kolory "rgb" albo współrzędne tekstury "stp" w zależności od widzimisię programisty.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #18 20076179
    ex-or
    Poziom 28  
    _lazor_ napisał:
    JacekCz napisał:
    ale znacznej uwagi tak, bo wspomagania od kompilatora zero.


    Dlaczego uważasz że nie ma "wspomagania" ze strony kompilatora? Brzmi to niedorzecznie.

    Może dlatego, że kompilator może niejawnie wyrównać pola struktury w sposób specyficzny dla danej architektury i konkretnej struktury? Jeżeli węzły sieci nie są homogeniczne (a IMHO obecnie jest to założenie bezdyskusyjne) to struktura wyprodukowana przez producenta może rozjechać się z przetworzoną przez konsumenta. No dobra, strukturę można spakować. A co jeśli w jakiejś architekturze dostęp do niewyrównanej danej powoduje wyjątek? Tak więc przy tym "rozwiązaniu" trzeba baaardzo głęboko przemyśleć efekty uboczne i te przemyślenia trzeba powtórzyć przy każdej modyfikacji struktury i dla każdej użytej (czy nawet potencjalnie użytej) architektury. Krótko mówiąc: popelina.
  • #19 20076346
    tmf
    VIP Zasłużony dla elektroda
    ex-or napisał:
    No dobra, strukturę można spakować. A co jeśli w jakiejś architekturze dostęp do niewyrównanej danej powoduje wyjątek?

    Taka sytuacja może mieć miejsce tylko przy pobieraniu wskaźnika do pola struktury, który potem wykorzystamy do dostępu do niewyrównanych danych. Normalnie kompilator wygeneruje kod realizujący bezpieczny dostęp do niewyrównanych danych - po prostu na niektórych platformach będzie to nieco wolniejsze. Nowsze gcc dają ostrzeżenia w przypadku niebezpiecznego odwoływania się do niewyrównanych danych.
    ex-or napisał:
    Tak więc przy tym "rozwiązaniu" trzeba baaardzo głęboko przemyśleć efekty uboczne i te przemyślenia trzeba powtórzyć przy każdej modyfikacji struktury i dla każdej użytej (czy nawet potencjalnie użytej) architektury.

    Ogólnie, programowanie polega na głębokim przemyśleniu tego co się robi. Więc tu unia niekoniecznie dokłada jakiś głębszych problemów. Niemniej przy przesyłaniu danych pomiędzy systemami zajdą oczywiste problemy z wielkością typów danych i endianess. Ale te problemy wystąpią niezależnie, czy ktoś użyje unii, czy nie.
    W wielu przypadkach, moim zdaniem, dostęp poprzez unię jest czytelniejszy, umożliwia także lepszą kontrolę typów (w C generalnie jest ona słaba, a rzutowanie na każdym kroku nie jest IMHO dobrym pomysłem).
  • #20 20076420
    JacekCz
    Poziom 42  
    ex-or napisał:
    _lazor_ napisał:
    JacekCz napisał:
    ale znacznej uwagi tak, bo wspomagania od kompilatora zero.


    Dlaczego uważasz że nie ma "wspomagania" ze strony kompilatora? Brzmi to niedorzecznie.

    Może dlatego, że kompilator może niejawnie wyrównać pola struktury w sposób specyficzny dla danej architektury i konkretnej struktury?


    To też.

    Ale w momencie pisania, sądzę miałem na myśli, że w kodzie C można podstawiać do pól z różnych wariantów unii. A nawet się odezwałem chyba przy unii anonimowej, albo nie był proponowany selektor.

    nadal uważam, że niewiele jest (oprócz "spawania się" do specyficznych rejestrów sprzętu i bardzo rzadko) dobrych wzorów na unię w C.
    Za to bardzo sympatyzuję za obiektowym wzorcem Variant w C++, który jest owrapowaną unią. Więc mam o unii ogólnie złe zdanie, z jednym całkiem pozytywnie ocenianym wyjątkiem.

    Do transmisji przez sieci nie uznaję unii jako rozwiązującej jakikolwiek problem - funkcje void * wystarczają.

Podsumowanie tematu

W dyskusji poruszono zastosowania unii i struktur w języku C, szczególnie w kontekście systemów embedded. Uczestnicy wskazali na rzadkie, ale praktyczne przykłady użycia unii, takie jak konwersja danych do formatu bajtowego dla kontrolerów DMA oraz dostęp do rejestrów z różnymi polami bitowymi. Przykłady obejmowały definicje unii z polami bitowymi dla rejestrów sprzętowych, co umożliwia łatwiejszy dostęp do poszczególnych bitów. Zauważono również, że unie mogą być użyteczne w systemach przesyłających dane w pakietach o stałej długości, gdzie różne typy danych są mieszane. Wskazano na alternatywy, takie jak makra, które mogą ukrywać walidację wartości, oraz na problemy związane z dostępem do niewyrównanych danych. Dyskusja podkreśliła, że wybór między unią a strukturą zależy od specyfiki projektu i wymagań sprzętowych.
Podsumowanie wygenerowane przez model językowy.
REKLAMA