Skocz do zawartości

Połączenie unii ze strukturą


Pomocna odpowiedź

Dziś tak usiadłem i staram się zrozumieć czym jest unia, jak z tego korzystać itp. Nie do końca jeszcze wszystko jest jasne, wiem tylko tyle, ze unia dostosowuje swój rozmiar do największego typu, jaki się w nim znajduje, czyli np:

union {
  uint8_t idx[6];
  uint16_t var;
}unia;

Rozmiar unii to 6B,a nie jak w przypadku struktury 8B. Znalazłem pare przykładów, które łączą unie i struktury wykorzystując pewna właściwość do przeprowadzania szybszych operacji na zmiennych strukturalnych. 

typedef union {
	uint8_t idx[6];
	struct {
		uint8_t godz;
		uint8_t min;
		uint8_t sek;
		uint8_t dni;
		uint8_t miesiace;
		uint8_t rok;
		};

}t_datetime;

t_datetime zegarek;

No i gdybym gdzieś w kodzie ustawił wartość:

zegarek.godz = 23;

A wyświetlił to zmienną w ten sposób:

uart_putint(zegarek.idx[0],10);

To wyświetlona wartość to będzie właśnie 23. Nie mogę do końca zrozumieć jak to działa.

Kombinowałem też z innym zapisem :

typedef struct {
	uint8_t godz;
	uint8_t min;
	uint8_t sek;
	uint8_t dni;
	uint8_t miesiace;
	uint8_t rok;
}t_dt;

typedef union {
	uint8_t idx[6];
	t_dt dataczas;
}t_datetime;

t_datetime zegarek;

Że jest to rozbite na unie i strukturę osobno, działanie identyczne, zajętość RAMu i FLASHa tez, tylko bezpośrednie odwoływanie do pól jest dłuższe bo wtedy jest:

zegarek.dataczas.godz = 1;

I zastanawiam się też, czy te dwie formy zapisu są sobie równoważne czy tylko ja nie dostrzegam różnicy prócz dłuzszego zapisu przy odwoływaniu się do pól. 

Sama ideę myślę, że rozumiem bo można w ten sposób w prosty i szybki sposób zapisać jakieś wartości do struktury np:

for (uint8_t i=0;i<5;i++)zegarek.idx[i] = 23;

I w ten sposób w 1 linii mam w każdym polu wartość 23. Rozumiem też, że tworząc tą zmienną tablicową w unii jej rozmiar musi się pokrywać z wielkością struktury. Teraz w toku rozważań przy takim rozwiązaniu nasunął się problem, co jeśli zmienne w strukturze będą różnych typów np: uint8_t,16,32? Wtedy taka operacja raczej nie przejdzie, tak sądze 😛 

Edytowano przez Krawi92
Link to post
Share on other sites

Po prostu elementy struktury są ułożone po kolei, a unii w tym samym miejscu w pamięci.

  • Pomogłeś! 1
Link to post
Share on other sites

Czyli rozumiem to tak :

typedef union {
	uint8_t idx[6];
	struct {
		uint8_t godz;
		uint8_t min;
		uint8_t sek;
		uint8_t dni;
		uint8_t miesiace;
		uint8_t rok;
		};

}t_datetime;

zmienna tablicowa w unii zajmuje 6 bajtów w pamięci powiedzmy pod adresem:

0x00

0x01

0x02

0x03

0x04

0x05

I zmienne w strukturze również, dlatego te wartości się pokrywają 

 

Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

A czy mógłbyś podać jeszcze może jakiś prosty przykład kiedy warto stosować unie, lub łączyć je ze strukturami ? Powiem szczerze, że nie często spotykam w różnych kodach unie. 

Link to post
Share on other sites

Tak na szybko - adres IPv4. Może być interpretowany jako 32-bitowa liczba, lub jako tablica 4 bajtów.

Zapisz sobie ip_addr_t jako unię

  • Pomogłeś! 1
Link to post
Share on other sites
(edytowany)

Wróciłem do swojego projektu zegara DCF i za radą autora książki "Język C dla mikrokontrolerów AVR. Od podstaw do zaawansowanych aplikacji." T.Francuza próbuję wykorzystać w programie unię. W pliku przykładowym autor proponuje taką konstrukcję:

typedef union
{
 struct  
 {
	uint8_t  begin       : 1;                //Bit startu
	uint16_t reserved    : 14;               //Zarezerwowane
	uint8_t  backupant   : 1;                //1 - antena pomocnicza, 0 - normalna
	uint8_t  TimeZoneChg : 1;                //1 - zapowiedź zmiany czasu
	uint8_t  TimeZone    : 2;                //10 - czas zimowy (UTC+1), 01 - czas letni (UTC+2)
	uint8_t  LeapSec     : 1;                //1- zapowiedź dodatkowej sekundy
	uint8_t  TimeSep     : 1;                //Separator pola czasu, zawsze 1
	uint8_t  Minute      : 7;                //Minuty w BCD (0-59)
	uint8_t  MinParity   : 1;                //Parzystość dla pola Minute
	uint8_t  Hour        : 6;                //Godziny w BCD (0-23)
	uint8_t  HourParity  : 1;                //Parzystość dla pola Mour
	uint8_t  Day         : 6;                //Dzień w BCD (1-31)
	uint8_t  DayNumber    : 3;               //Numer dnia (1-Poniedziałek, 7-Niedziela)
	uint8_t  Month       : 5;                //Miesiąc w BCD (1-12)
	uint8_t  Year        : 8;                //Rok w BCD (00-99)
	uint8_t  DateParity  : 1;                //Parzystość dla pól Day-Year
	uint8_t  LastBit     : 1;                //Ostatni bit kończący ramkę
 };
 uint8_t Byte[0];                            //Umożliwia bitowy dostęp do powyższej struktury 
} DCF77_t;

I prawie wszystko rozumiem, tylko dlaczego Byte[0]? W kodzie przykładu zmienna ta wykorzystywana jest do operacji na bitach struktury np. tak:

DCF77Record[DCF77RecNo].Byte[DCF77_bitcounter / 8] &= ~(1<<DCF77_bitcounter % 8);

Generalnie to działa lecz mam wątpliwości. Jak interpretować taką deklarację uint8_t  Byte[0]? Jeśli np. DCF_bitcounter = 50 to powyższy kod odwołuje się do Byte[6]. Czy to nie poza zakresem Byte[]? Czy aby Byte[] nie powinno mieć rozmiaru struktury w bajtach, lub większego?

Edytowano przez Belferek
Link to post
Share on other sites
53 minuty temu, Belferek napisał:

Generalnie to działa lecz mam wątpliwości. Jak interpretować taką deklarację uint8_t  Byte[0]? Jeśli np. DCF_bitcounter = 50 to powyższy kod odwołuje się do Byte[6]. Czy to nie poza zakresem Byte[]? Czy aby Byte[] nie powinno mieć rozmiaru struktury w bajtach, lub większego?

To jak autor książki to napisał, jest zwyczajnie błędne w bieżących i rozwojowych standardach zarówno C jak i C++. GNU C dopuszcza taką wersję. Więc w zależności od kompilatora: nie skompiluje się, skompiluje się z ostrzeżeniem lub zostanie przyjęte z radością. Z logicznego punktu widzenia ma to sens, bo C nie sprawdza zakresu w chwili dostępu. Siłą natury unii, ta unia będzie miała rozmiar równy największemu z jej elementów, czyli w powyższym przykładzie strukturze.

Taki przykład:

void f()
{
	int x[1];
	x[50]++;
}

To skompiluje się bez zająknięcia, choćbyś dodał "-Wall -pedantic -Werror". To wynika z tego czym jest tak naprawdę []. Opisywałem to w innej wiadomości.

Wartość, którą podasz w definicji zmiennej informuje ile pamięci jej przydzielić (w tym wypadku na stosie) i to koniec jej znaczenia. Potem "x" traktowane jest jak wskaźnik bez żadnej wiedzy o jego rozmiarze. Może to prowadzić do błędów (często stąd biorą się ataki sieciowe na kod w C), ale z kolei kod działa szybciej, gdy nie jest sprawdzany zakres przy dostępie.

Ergo dostęp do Byte[6] nie jest w żaden sposób ograniczony przez kompilator czy w czasie uruchomienia, może co najwyżej skończyć się błędem gdyby tyczył się miejsca, które nie należy do sekcji pamięci programu i dozwolonej do zapisu, no ale z racji tego, że ta struktura ma, o ile dobrze policzyłem na palcach, 8 bajtów, to będzie działać.

  • Lubię! 1
Link to post
Share on other sites

Tak tylko na marginesie. Poprawny zapis odporny na C i C++ mógłby wyglądać przykładowo:

typedef union
{
 struct fields
 {
	uint8_t  begin       : 1;                //Bit startu
	uint16_t reserved    : 14;               //Zarezerwowane
	uint8_t  backupant   : 1;                //1 - antena pomocnicza, 0 - normalna
	uint8_t  TimeZoneChg : 1;                //1 - zapowiedź zmiany czasu
	uint8_t  TimeZone    : 2;                //10 - czas zimowy (UTC+1), 01 - czas letni (UTC+2)
	uint8_t  LeapSec     : 1;                //1- zapowiedź dodatkowej sekundy
	uint8_t  TimeSep     : 1;                //Separator pola czasu, zawsze 1
	uint8_t  Minute      : 7;                //Minuty w BCD (0-59)
	uint8_t  MinParity   : 1;                //Parzystość dla pola Minute
	uint8_t  Hour        : 6;                //Godziny w BCD (0-23)
	uint8_t  HourParity  : 1;                //Parzystość dla pola Mour
	uint8_t  Day         : 6;                //Dzień w BCD (1-31)
	uint8_t  DayNumber    : 3;               //Numer dnia (1-Poniedziałek, 7-Niedziela)
	uint8_t  Month       : 5;                //Miesiąc w BCD (1-12)
	uint8_t  Year        : 8;                //Rok w BCD (00-99)
	uint8_t  DateParity  : 1;                //Parzystość dla pól Day-Year
	uint8_t  LastBit     : 1;                //Ostatni bit kończący ramkę
 } fields;
 uint8_t Byte[sizeof(struct fields)];                            //Umożliwia bitowy dostęp do powyższej struktury 
} DCF77_t;

I do pól należałoby się odwoływać w formie "zmienna_dcf77.fields.Day".

  • Lubię! 1
Link to post
Share on other sites

No to już wiem więcej - dziękuję za wyjaśnienie.

10 minut temu, trainee napisał:

I do pól należałoby się odwoływać w formie "zmienna_dcf77.fields.Day".

Nie nazywając tej struktury do jej pól można się chyba z powodzeniem odwoływać jako np. zmienna_dcf77.Day ?

Link to post
Share on other sites
(edytowany)

Każdy typ zmiennej można zrzutować do tablicy. Także najbardziej wymyślny typ strukturalny można przedstawić nawet jako char[] to jest dosyć brzydkie podejście ale jak się go zrozumie, jak działa, jest w wyniku przyjemnym narzędziem 

Edytowano przez _LM_
Link to post
Share on other sites
(edytowany)
36 minut temu, Belferek napisał:

Nie nazywając tej struktury do jej pól można się chyba z powodzeniem odwoływać jako np. zmienna_dcf77.Day ?

Tak. Przy czym anonimowe struktury (czyli takie nienazwane) w unii są dozwolone tylko w C, w kompilatorach zgodnych z C11 wzwyż (czyli w zasadzie od 2012 roku). W C++ nie są dozwolone i nie ma ich w planach również w potencjalnie przyszłorocznej aktualizacji standardu. Oczywiście jedną sprawą są standardy, inną są rozszerzenia danych kompilatorów, jak np. wspominany kompilator GNU. Tam w trybie z jego rozszerzeniami (domyślny), przejdzie anonimowa i w C++.

Zupełnie osobista moja uwaga: dlatego mam lekkie ciarki gdy ktoś używa zbitki "C/C++". To są dwa osobne języki, idące osobnymi drogami. Osobiście wolę C, bo C++ jest przeładowany do bólu.

Edytowano przez trainee
  • Lubię! 1
Link to post
Share on other sites

@trainee mam pytanie odnosnie mojego pierwszego posta. Czy te 2 zapisy, jeden gdzie w uni znajduje się struktura i potem rozbite na 2 części są sobie równoznaczne jeśli chodzi o działanie? 

Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.