C++ Przesunięcia bitowe czterech zmiennych w jedną nową zmienną

0

Cześć! To mój pierwszy post na forum, więc witam wszystkich i proszę o podpowiedź...

Chcę stworzyć większy projekt w C++ i do tej pory jakoś mi szło, a jak już trafiłem na większy problem to w większości przypadków Google pomagało, aż tego problemu który mam teraz...
Wiem, że to dość dziwnie wygląda, "robi duży projekt a nawet przesunięć bitowych nie umie zrobić" ale totalnie nie mam pomysłu czemu tak mi się sypią dane.

Założenia:
Mam cztery zmienne typu char które mają się złożyć na większą zmienną typu unsigned int.

Problem:
Przesunięcia bitowe nie dzałają tak, jakbym się tego spodziewał, to znaczy psują mi całą wartość zmiennej wypełniając ją jedynkami.

int main()
{
	char a = 0b11110001;
	char b = 0b11100011;
	char c = 0b11000111;
	char d = 0b10001111;
	unsigned all = 0;
	
	print_bits(bin_, &a); //ta funkcja wyświetla zmenną w postaci bitowej
	print_bits(bin_, &b);
	print_bits(bin_, &c);
	print_bits(bin_, &d); //tekst w konsoli:
	//11110001111000111100011110001111
	
	cout << endl;
	
	all |= ( (a << 24) | (b << 16) | (c << 8) | d );
	
	print_bits(bin_, &all);
	
	cout << endl;
}

W teorii powinienem dostać dwie takie same linijki
11110001111000111100011110001111
11110001111000111100011110001111

Gdzie pierwsza to wyświetlone po kolei zmienne (a, b, c, d) a druga to zmienna pomocnicza (all) zawierająca wynik operacji OR na tych zmiennych.
Jednak dostaję coś zupełnie innego...

11110001111000111100011110001111
11111111111111111111111110001111

Zmieniłem trochę wartości tych zmiennych na takie...

char a = 0b01110001;
char b = 0b01100011;
char c = 0b01000111;
char d = 0b00001111;

Zmieniłem tylko wartość MSB na 0, a jak się okazało, to wystarczyło by otrzymać dwa takie same wyniki w konsoli.
01110001011000110100011100001111
01110001011000110100011100001111

Ktoś wie czemu to tak działa ? Przecież to tylko przesunięcie każdej ze zmiennych typu char na swoje miejsce w zmiennej typu unsigned int.

Kod funkcji print_bits

print_bits(enum_mode _mode, T_data *_data)
{
	if(_mode == bin_)
	{
		for(int i = ( (7 * sizeof(*_data)) + (sizeof(*_data) - 1); i >= 0; i--)
		{
			cout << (0 + ((*_data >> i) & 1));
		}
	}
}
3

Użyj unsigned char

0

Ym... zapomnijcie o tym temacie...
To było co najmniej głupie...
Mam tylko drobną prośbę, czemu unsigned char nie pasuje do unsigned int ?
Przecież jedno i drugie to po prostu bity. Wiem, że unsigned char może zawierać wartość 0 - 255 a zwykły char 127-128
**więc dlaczego zwykłe przesunięcie tych bitów nie działa ? **

2

Pasuje, tylko że zasada przesunięcia int a unsigned int jest inna.

0

Dziękuję i pozdrawiam :)

4
MrWeb123 napisał(a):

Ktoś wie czemu to tak działa ?
Odpowiedź @_13th_Dragon​a mówi jak tego problemu uniknąć, ale nie odpowiada na to pytanie.

  1. ze względu na Twoją implementację. signed char, char i unsigned char to trzy różne typy. char może mieć znak lub nie - standard tego nie definiuje. Tutaj oczywiście ma znak. Niestety, signed integer overflow to UB (przypisujesz >127 do typu, który ma zakres wartości do 127), więc teoretycznie dalsza część wywodu nie ma znaczenia (ale mimo wszystko poprawnie obrazuje co się dzieje).

  2. ze względu na Twoją implementację#2. char nie musi mieć koniecznie 8 bitów. Gdyby miał więcej to nie miałbyś integer overflow, a byłoby zgodne z Twoimi przewidywaniami.

  3. ze względu na Twoją implementację#3. Typy ze znakiem w Twojej implementacji są zrealizowane za pomocą kodu uzupełnień do 2. Standard zezwala również (chociaż jeszcze nie spotkałem się z tym w praktyce) na użycie kodu uzupełnień do 1, gdzie uzyskałbyś zupełnie inne wyniki.

  4. ze względu na zasadę działania operatorów przyjmujących 2 argumenty dla typów całkowitych. Pierwszy istotny fragment standardu:

Standard C++14 (N3797) §5 [expr] /10 napisał(a)

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:
[...]
— Otherwise, the integral promotions shall be performed on both operands. Then the following rules shall be applied to the promoted operands:
— If both operands have the same type, no further conversion is needed.
[...]
— Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

Czyli najpierw wykonywane są integral promotions. Istotny fragment:

Standard C++14 (N3797) §4.5 [conv.prom] /1 napisał(a)

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

I do kompletu conversion ranks:

Standard C++14 (N3797) §4.13 [conv.rank] /1 napisał(a)

Every integer type has an integer conversion rank defined as follows:
[...]
— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.

Teraz już wiesz wszystko, czego potrzebujesz aby zrozumieć dlaczego dostajesz taki wynik, a nie inny. Operator sumy logicznej | dla typów char i char, najpierw powoduje ich konwersję do inta, a dopiero potem oblicza wynik. Dlatego, Twoje d zostanie skonwertowane z wartości 0b10001111 do 0b11111111'11111111'11111111'10001111. Dlaczego suma logiczna tej wartości i dowolnej innej ma na na początku 24 jedynki - myślę, że jest oczywiste.

1 użytkowników online, w tym zalogowanych: 0, gości: 1