Unia w C++ i przypisanie wartości zmiennym składowym


(Kopczynski1991) #1

Unie mogą przechowywać w jednej chwili wartość w jednej składowej, czyli:

#include 

#include 

using namespace std;

union spacer

{

	int mil;

	double metrow;

};

int main(int argc, char *argv[]) 

{

	spacer Wojtek;

	Wojtek.mil=321;

	cout<<"Wojtek w milach: "<
	Wojtek.metrow=100;

	cout<<"Wojtek w metrach: "<
	cout<<"Wojtek w milach: "<
	//tak wlasnie powinno reagowac

	getch();

	return 0;

}

Ten przykład się zgadzał, ale jeśli zamienię w unii deklaracje składowych tzn ich typ czyli int na double i double na int, czyli:

#include 

#include 

using namespace std;

union spacer

{

	double mil;

	int metrow;

};

int main(int argc, char *argv[]) 

{

	spacer Wojtek;

	Wojtek.mil=321;

	cout<<"Wojtek w milach: "<
	Wojtek.metrow=100;

	cout<<"Wojtek w metrach: "<
	cout<<"Wojtek w milach: "<
	//tak nie powinno reagowac

	getch();

	return 0;

}

Może ktoś wytłumaczyć czemu w tym drugim przykładzie czemu “Wojtek.mil” jest wyświetlana prawidłowa liczba a nie przypadkowa. Przecież zmieniłem drugą zmienną składową więc ta inna powinna być wymazana i przypadkowa?


(Matadini) #2

Moim zdaniem dużo zależy od środowiska w którym pracujesz, czy to jest Visual Studio, DevCpp czy CodeBlock. Mi osobiście zdarzały się przypadki że przykłady z Deva nie szły w VS albo przykłady działający w CB już nie ruszał w Devie - czego się tak działo mimo że na ludzki rozum wszystko było tak jak trzeba? Pytaj mnie a ja Ciebie. :smiley:


(Witos) #3

Wszystko reaguje tak jak powinno :slight_smile:

Chodzi o to, że typy w unii “dzielą” wspólny obszar pamięci.

Double ma 8 bajtów, int ma 4 bajty (zarówno platformy IA-32 i AMD64), w związku z tym pola te nakładają się, ale nie całkowicie.

Liczbę zmiennoprzecinkową zapisuje się w postaci [znak] mantysa * (2 ^ wykładnik) (patrz: http://pl.wikipedia.org/wiki/Liczba_zmiennoprzecinkowa).

Dla double na znak jest 1 bit, wykładnik zajmuje 11 bit, mantysa 52 bit( +1 bit ukryty). Dla liczb znormalizowanych mantysa jest z przedziału <0.5;1.0).

Więcej informacji o reprezentacji w formacie IEE754: http://en.wikipedia.org/wiki/Floating_point#Internal_representation

Przy kolejności bajtów typu “little endian” wartość inta nakłada się z młodszymi 32 bitami mantysy, Tak więc masz mantysę 52-bitową i niszczysz 32 młodsze bity. Różnica może być niezauważalna przy domyślnej precyzji wypisywania liczb zmiennoprzecinkowych (szczególnie, że niekoniecznie wszystkie 32 bity sie zmieniają, w tym szczególnym przypadku może być to jedynie kilka najmłodszych bitów, da się to policzyć).


(Razi) #4

Unie nie służą do przechowywania danych w jednej składowej, tylko do danych różnych typów na jednym fragmencie pamięci. Inaczej mówiąc: unia to obszar pamięci, na którym siedzi kilka zmiennych jednocześnie, rozmiar unii to rozmiar największego elementu. Jak zostanie ten fragment pamięci zinterpretowany, zależy od tego, do czego się odwołasz. Przykładowo:

union liczba{

 short i16;

 int i32;

 char byte[4];

};

ma rozmiar 4 bajtów.

Jeśli przypiszesz do i32 jakąś liczbę, nadpiszesz całe 4 bajty. Możesz potem w prosty sposób odczytać poszczególne bajty z tego chara*. Po prostu program dany fragment pamięci będzie traktował tak, jak się do niego odwołasz. Jakbyś i32 nadał 0, a potem i16 np. 1345, to przy wypisaniu i32 też wypisałby 1345. Natomiast gdybyś do i32 przypisał np. 0xBBAADDCC, to przy wypisaniu i16 wypisałby 0xDDCC- 2 pierwsze bajty, tzn. najmniej znaczące, obecne komputery pracują na little-endian, byte[2] natomiast miałby wartość 0xAA.

Nie ma tam żadnej liczby przypadkowej, jak twierdzisz, a jeśli tak napisali w książce w której to wyczytałeś: autor się mylił lub po prostu nie wytłumaczył tego poprawnie (lub źle zrozumiałeś). Wcześniej przypisałeś coś do “metrow”, czyli double’a, czyli nadpisałeś całą unię (największy element). Skoro “mil” był intem, a przypisałeś chwilę wcześniej doubla, który ma zupełnie inną formę binarną, może się wydać że wypisał wartość losową. Tak faktycznie wypisał zinterpretowane 4 pierwsze bajty tego double jako int.

A czemu tak w tym drugim? Zaokrąglenie:

Bez nadpisywania metrów: 321.00000000000000000000

Z nadpisywaniem metrów: 321.00000000000568434189

Te 100 się zapisało akurat tam gdzieś, że robi taki ułamek. Poczytaj o sposobie zapisywania liczb zmiennoprzecinkowych.


(Kopczynski1991) #5

Aha. Właśnie autor tej książki twierdził, że liczby są niszczone itd więc dlatego nie mogłem tego zrozumieć… Już mniej więcej to kapuje:) Poczytam sobie jeszcze dokładnie o tych liczbach zmiennoprzecinkowych… Dzięki za informacje:)


(Rolek0) #6

Polecam: http://gynvael.coldwind.pl/?id=374 :slight_smile: