Widziałem ten wzorzec często używany w C i C ++.
unsigned int flags = -1; // all bits are true
Czy to dobry przenośny sposób, aby to osiągnąć? Czy używa 0xffffffff
lub ~0
lepiej?
Widziałem ten wzorzec często używany w C i C ++.
unsigned int flags = -1; // all bits are true
Czy to dobry przenośny sposób, aby to osiągnąć? Czy używa 0xffffffff
lub ~0
lepiej?
-1
zawsze będzie działać, fakt, że potrzebny jest komentarz po tym, jak pokazuje, że nie jest to jasny kod. Jeśli zmienna ma być zbiorem flag, po co przypisywać jej liczbę całkowitą? Jego typ może być liczbą całkowitą, ale z pewnością nie jest semantycznie liczbą całkowitą. Nigdy go nie zwiększysz ani nie pomnożysz. Więc użyłbym 0xffffffff
nie dla przenośności lub poprawności, ale dla przejrzystości.
-1
pozostaje przenośnym i wstecznie kompatybilnym rozwiązaniem dla obu języków, ale może wpłynąć na niektóre argumenty w innych odpowiedziach.
Odpowiedzi:
Polecam zrobić to dokładnie tak, jak pokazałeś, ponieważ jest to najprostsze rozwiązanie. Inicjalizacja, -1
która będzie działać zawsze , niezależnie od rzeczywistej reprezentacji znaku, a~
czasami będzie miała zaskakujące zachowanie, ponieważ będziesz musiał mieć odpowiedni typ operandu. Tylko wtedy uzyskasz najwyższą wartość unsigned
typu.
Jako przykład możliwej niespodzianki rozważ tę:
unsigned long a = ~0u;
Nie musi koniecznie przechowywać wzorca ze wszystkimi bitami od 1 do a
. Ale najpierw utworzy wzorzec ze wszystkimi bitami 1 w an unsigned int
, a następnie przypisze go doa
. To, co się dzieje, gdy unsigned long
ma więcej bitów, to fakt, że nie wszystkie mają wartość 1.
Rozważmy ten, który zawiedzie na reprezentacji dopełnienia innej niż dwa:
unsigned int a = ~0; // Should have done ~0u !
Powodem tego jest to, że ~0
wszystkie bity muszą być odwrócone. Odwrócenie da wynik -1
na maszynie z dopełnieniem do dwóch (jest to wartość, której potrzebujemy!), Ale nie przyniesie -1
innego wyniku. Na maszynie z dopełnieniem do jedynki daje zero. Zatem na maszynie z dopełnieniem jedynki powyższe zostanie zainicjalizowane a
do zera.
Należy zrozumieć, że chodzi o wartości, a nie o bity. Zmienna jest inicjalizowana wartością . Jeśli w inicjatorze zmodyfikujesz bity zmiennej używanej do inicjalizacji, wartość zostanie wygenerowana zgodnie z tymi bitami. Wartość, której potrzebujesz, aby zainicjować a
do najwyższej możliwej wartości, to -1
lub UINT_MAX
. Drugi będzie zależał od typu a
- będziesz musiał użyć ULONG_MAX
do unsigned long
. Jednak pierwszy nie będzie zależał od jego rodzaju i jest to fajny sposób na uzyskanie jak największej wartości.
My nie mówimy o tym, czy -1
ma wszystkie bity jednego (to nie zawsze musi). I nie mówimy o tym, czy ~0
ma wszystkie bity jeden (oczywiście ma).
Ale mówimy o tym, jaki jest wynik zainicjowanej flags
zmiennej. I do tego będzie działać tylko-1
z każdym typem i maszyną.
numeric_limits<size_t>::max()
jest trochę rozwlekła, ale obsada też ...
-1
są reprezentowane, ani nie pyta, jakie bity ~0
mają. Możemy nie przejmować się wartościami, ale kompilator tak. Nie możemy ignorować faktu, że operacje działają z wartościami i według wartości. Wartość z ~0
może nie być -1
, ale jest to wartość, którą trzeba. Zobacz moją odpowiedź i podsumowanie @ Dingo.
unsigned int flags = -1;
jest przenośny.unsigned int flags = ~0;
nie jest przenośny, ponieważ opiera się na reprezentacji dopełnienia do dwóch.unsigned int flags = 0xffffffff;
nie jest przenośny, ponieważ zakłada 32-bitowe ints.Jeśli chcesz ustawić wszystkie bity w sposób gwarantowany przez standard C, użyj pierwszego.
~0
daje int
oczywiście wartość z ustawionymi wszystkimi bitami. Ale przypisania int
do unsigned int
nie koniecznie doprowadzić do unsigned int posiadające ten sam wzór bitowy jako podpisanego bitów. Tylko w przypadku reprezentacji dopełnienia do 2 jest tak zawsze. W przypadku uzupełnienia 1s lub reprezentacji wielkości znaku, przypisanie int
wartości ujemnej do unsigned int
wyników w innym wzorze bitowym. Dzieje się tak, ponieważ standard C ++ definiuje konwersję ze znakiem -> bez znaku jako wartość równą modulo, a nie wartość z tymi samymi bitami.
Szczerze mówiąc, myślę, że wszystkie fff są bardziej czytelne. Jeśli chodzi o komentarz, że jest to anty-wzór, jeśli naprawdę zależy ci na tym, aby wszystkie bity zostały ustawione / wyczyszczone, argumentowałbym, że prawdopodobnie jesteś w sytuacji, w której i tak zależy ci na wielkości zmiennej, co wymagałoby czegoś takiego jak boost :: uint16_t itp.
Aby uniknąć wymienionych problemów, wystarczy wykonać następujące czynności:
unsigned int flags = 0;
flags = ~flags;
Przenośne i na temat.
flags
jako const
.
~0
jest liczbą całkowitą, która ma wszystkie bity ustawione na 1, ale kiedy następnie przypisujesz int
ją do unsigned
zmiennej flags
, wykonujesz konwersję wartości z -2**31
(zakładając 32-bitową int
) na (-2**31 % 2**32) == 2**31
, która jest liczbą całkowitą ze wszystkimi bitami oprócz pierwszego ustawionego na 1.
u
przyrostka w Twojej odpowiedzi. To oczywiście zadziałałoby, ale nadal występuje problem z określeniem używanego typu danych ( unsigned
i nie większego) dwukrotnie, co może prowadzić do błędów. Błąd najprawdopodobniej pojawi się, jeśli przypisanie i początkowa deklaracja zmiennej są bardziej od siebie oddalone.
Nie jestem pewien, czy używanie niepodpisanego int dla flag jest dobrym pomysłem w C ++. A co z bitsetami i tym podobnymi?
std::numeric_limit<unsigned int>::max()
jest lepsze, ponieważ 0xffffffff
zakłada, że unsigned int jest 32-bitową liczbą całkowitą.
auto
. auto const flags = std::numeric_limit<unsigned>::max()
.
unsigned int flags = -1; // all bits are true
„Czy to dobry [,] przenośny sposób, aby to osiągnąć?”
Przenośny? tak .
Dobry? Sporny , o czym świadczy całe zamieszanie pokazane w tym wątku. Wyjaśnienie na tyle jasno, że inni programiści mogą zrozumieć kod bez zamieszania, powinno być jednym z wymiarów, które mierzymy dla dobrego kodu.
Ponadto ta metoda jest podatna na ostrzeżenia kompilatora . Aby uniknąć ostrzeżenia bez uszkadzania kompilatora, potrzebujesz jawnego rzutowania. Na przykład,
unsigned int flags = static_cast<unsigned int>(-1);
Jawna obsada wymaga zwrócenia uwagi na typ celu. Jeśli zwracasz uwagę na typ celu, naturalnie unikniesz pułapek innych podejść.
Moją radą byłoby zwrócenie uwagi na typ docelowy i upewnienie się, że nie ma niejawnych konwersji. Na przykład:
unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);
Z których wszystkie są poprawne i bardziej oczywiste dla innych programistów.
A z C ++ 11 : możemy użyć, auto
aby uczynić którekolwiek z nich jeszcze prostszym:
auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);
Uważam, że poprawne i oczywiste jest lepsze niż po prostu poprawne.
Konwersja -1 na dowolny typ bez znaku jest gwarantowana przez standard w celu uzyskania wszystkich jedynek. Używanie of ~0U
jest ogólnie złe, ponieważ 0
ma typ unsigned int
i nie wypełni wszystkich bitów większego typu bez znaku, chyba że wyraźnie napiszesz coś takiego ~0ULL
. W rozsądnych systemach ~0
powinien być identyczny z-1
, ale ponieważ standard zezwala na reprezentacje jedności-dopełnienia i znaku / wielkości, ściśle mówiąc, nie jest przenośny.
Oczywiście zawsze dobrze jest napisać, 0xffffffff
jeśli wiesz, że potrzebujesz dokładnie 32 bitów, ale -1 ma tę zaletę, że będzie działać w dowolnym kontekście, nawet jeśli nie znasz rozmiaru typu, takiego jak makra działające na wielu typach lub jeśli rozmiar typu różni się w zależności od implementacji. Jeśli nie wiesz, typ, inny bezpieczny sposób, aby uzyskać wszystkie-onów makr jest dopuszczalne UINT_MAX
, ULONG_MAX
, ULLONG_MAX
, itd.
Osobiście zawsze używam -1. To zawsze działa i nie musisz o tym myśleć.
~(type)0
(no cóż, wypełnij type
oczywiście po prawej stronie ). Rzucenie zera nadal daje zero, więc jest to jasne, a negowanie wszystkich bitów w typie docelowym jest dość jasno zdefiniowane. Jednak nie jest tak często, że chcę tę operację; YMMV.
neg
instrukcji. Maszyny, które mają fałszywe zachowanie arytmetyczne ze znakiem, mają oddzielne opkody arytmetyczne ze znakiem / bez znaku. Oczywiście naprawdę dobry kompilator po prostu zawsze ignorowałby podpisane rozkazy, nawet dla wartości ze znakiem, a tym samym otrzymywałby uzupełnienie do dwóch za darmo.
var = ~(0*var)
Sprawa zakończy się niepowodzeniem, jeśli var
typ bez znaku będzie węższy niż int
. Może var = ~(0U*var)
? (osobiście nadal wolę -1
).
Tak długo, jak masz #include <limits.h>
jeden z twoich elementów, powinieneś po prostu użyć
unsigned int flags = UINT_MAX;
Jeśli chcesz mieć długie bity, możesz użyć
unsigned long flags = ULONG_MAX;
Te wartości gwarantują, że wszystkie bity wartości wyniku będą ustawione na 1, niezależnie od sposobu implementacji liczb całkowitych ze znakiem.
Tak. Jak wspomniano w innych odpowiedziach, -1
jest najbardziej przenośny; jednak nie jest to zbyt semantyczne i wyzwala ostrzeżenia kompilatora.
Aby rozwiązać te problemy, wypróbuj tego prostego pomocnika:
static const struct All1s
{
template<typename UnsignedType>
inline operator UnsignedType(void) const
{
static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
return static_cast<UnsignedType>(-1);
}
} ALL_BITS_TRUE;
Stosowanie:
unsigned a = ALL_BITS_TRUE;
uint8_t b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;
ALL_BITS_TRUE ^ a
gdzie a
jest liczba całkowita ze znakiem? Typ pozostaje liczbą całkowitą ze znakiem, a wzór bitowy (reprezentacja obiektu) zależy od tego, czy cel jest dopełnieniem do 2, czy nie.
ALL_BITS_TRUE ^ a
powoduje błąd kompilacji, ponieważ ALL_BITS_TRUE
jest niejednoznaczny. Można go jednak użyć w uint32_t(ALL_BITS_TRUE) ^ a
ten sposób. Możesz spróbować samemu na cpp.sh :) Obecnie dodałbym static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
w, operator
aby upewnić się, że użytkownicy nie próbują używać int(ALL_BITS_TRUE)
. Zaktualizuję odpowiedź.
Nie zrobiłbym -1 rzeczy. To raczej nieintuicyjne (przynajmniej dla mnie). Przypisanie podpisanych danych do niepodpisanej zmiennej wydaje się po prostu naruszeniem naturalnego porządku rzeczy.
W twojej sytuacji zawsze używam 0xFFFF
. (Oczywiście użyj odpowiedniej liczby F dla zmiennej wielkości).
[Swoją drogą, bardzo rzadko widzę sztuczkę -1 wykonaną w prawdziwym kodzie.]
Dodatkowo, jeśli troszczą się o poszczególnych bitów w vairable, byłoby to dobry pomysł, aby rozpocząć używanie stałej szerokości uint8_t
, uint16_t
, uint32_t
rodzaje.
Na procesorach Intel IA-32 można zapisać 0xFFFFFFFF w rejestrze 64-bitowym i uzyskać oczekiwane wyniki. Dzieje się tak, ponieważ IA32e (64-bitowe rozszerzenie IA32) obsługuje tylko 32-bitowe natychmiastowe. W instrukcjach 64-bitowych 32-bitowe natychmiastowe znaki są rozszerzane do 64-bitowych.
Następujące jest nielegalne:
mov rax, 0ffffffffffffffffh
Poniższe umieszcza 64 jedynki w RAX:
mov rax, 0ffffffffh
Aby uzyskać kompletność, poniższe umieszczenie 32 1s w dolnej części RAX (aka EAX):
mov eax, 0ffffffffh
W rzeczywistości miałem programy, które zawodziły, gdy chciałem napisać 0xffffffff do zmiennej 64-bitowej, a zamiast tego otrzymałem 0xffffffffffffffff. W C byłoby to:
uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);
wynik to:
x is 0xffffffffffffffff
Myślałem, że opublikuję to jako komentarz do wszystkich odpowiedzi, które mówiły, że 0xFFFFFFFF zakłada 32 bity, ale tak wiele osób odpowiedziało na to, pomyślałem, że dodam to jako osobną odpowiedź.
UINT64_C(0xffffffff)
rozwija się do czegoś podobnego 0xffffffffuLL
, jest to zdecydowanie błąd kompilatora. Standard C w dużej mierze omawia wartości , wartość reprezentowana przez 0xffffffff
to 4294967295 (a nie 36893488147419103231) i nie widać żadnych konwersji do typów całkowitych ze znakiem.
Zobacz odpowiedź litb, aby uzyskać bardzo jasne wyjaśnienie problemów.
Nie zgadzam się z tym, że mówiąc ściśle, nie ma gwarancji w żadnym przypadku. Nie znam żadnej architektury, która nie reprezentuje wartości bez znaku `` jeden mniej niż dwa do potęgi liczby bitów '' jako wszystkich ustawionych bitów, ale oto, co tak naprawdę mówi Standard (3.9.1 / 7 plus przypis 44):
Reprezentacje typów całkowitych będą definiować wartości przy użyciu czystego binarnego systemu liczbowego. [Uwaga 44:] Reprezentacja pozycyjna liczb całkowitych wykorzystująca cyfry binarne 0 i 1, w której wartości reprezentowane przez kolejne bity są addytywne, rozpoczynają się od 1 i są mnożone przez kolejną moc całkującą 2, z wyjątkiem być może bitu z najwyższa pozycja.
To pozostawia możliwość, że jeden z bitów będzie w ogóle cokolwiek.
Chociaż 0xFFFF
(lub 0xFFFFFFFF
itp.) Może być łatwiejszy do odczytania, może zepsuć przenośność w kodzie, który w przeciwnym razie byłby przenośny. Rozważmy na przykład procedurę biblioteczną, która liczy, ile elementów w strukturze danych ma ustawione określone bity (dokładne bity są określone przez wywołującego). Procedura może być całkowicie niezależna od tego, co reprezentują bity, ale nadal musi mieć stałą „ustawienie wszystkich bitów”. W takim przypadku -1 będzie znacznie lepsze niż stała szesnastkowa, ponieważ będzie działać z dowolnym rozmiarem bitu.
Inną możliwością, jeśli jako typedef
maskę bitową jest używana wartość, byłoby użycie ~ (bitMaskType) 0; jeśli maska bitowa jest tylko typu 16-bitowego, to wyrażenie będzie miało ustawione tylko 16 bitów (nawet jeśli 'int' to w przeciwnym razie 32 bity), ale ponieważ 16 bitów to wszystko, co jest wymagane, wszystko powinno być w porządku, pod warunkiem, że faktycznie używa odpowiedniego typu w rzutowaniu typów.
Nawiasem mówiąc, wyrażenia formularza longvar &= ~[hex_constant]
mają nieprzyjemny błąd, jeśli stała szesnastkowa jest zbyt duża, aby zmieścić się w elemencie int
, ale będzie pasować do unsigned int
. Jeśli an int
ma 16 bitów, to longvar &= ~0x4000;
lub longvar &= ~0x10000
; wyczyści jeden bit longvar
, ale longvar &= ~0x8000;
usunie bit 15 i wszystkie bity powyżej. Wartości, które pasują, int
będą miały operator uzupełnienia zastosowany do typu int
, ale wynik zostanie rozszerzony do znaku long
, ustawiając górne bity. W przypadku wartości, które są zbyt duże, do typu unsigned int
zostanie zastosowany operator uzupełnienia long
. Wartości znajdujące się między tymi rozmiarami będą jednak stosować operator dopełniacza do typu unsigned int
, który zostanie następnie przekonwertowany na typ long
bez rozszerzenia znaku.
Praktycznie: tak
Teoretycznie: nie.
-1 = 0xFFFFFFFF (lub jakikolwiek rozmiar int jest na twojej platformie) jest prawdą tylko z arytmetyką dopełnienia do dwóch. W praktyce to zadziała, ale istnieją starsze maszyny (komputery mainframe IBM itp.), Na których masz rzeczywisty bit znaku zamiast reprezentacji dopełnienia do dwójki. Proponowane przez Ciebie rozwiązanie ~ 0 powinno działać wszędzie.
Jak wspominali inni, -1 to poprawny sposób na utworzenie liczby całkowitej, która zostanie przekonwertowana na typ bez znaku z wszystkimi bitami ustawionymi na 1. Jednak najważniejszą rzeczą w C ++ jest użycie poprawnych typów. Dlatego prawidłowa odpowiedź na Twój problem (obejmująca odpowiedź na zadane przez Ciebie pytanie) jest następująca:
std::bitset<32> const flags(-1);
Będzie to zawsze zawierać dokładną ilość potrzebnych bitów. Konstruuje a std::bitset
ze wszystkimi bitami ustawionymi na 1 z tych samych powodów, co w innych odpowiedziach.
Jest to na pewno bezpieczne, ponieważ -1 zawsze będzie miał ustawione wszystkie dostępne bity, ale wolę ~ 0. -1 po prostu nie ma większego sensu w przypadku pliku unsigned int
. 0xFF
... nie jest dobre, ponieważ zależy to od szerokości czcionki.
Wykorzystując fakt, że przypisanie wszystkich bitów do jednego dla typu bez znaku jest równoznaczne z przyjęciem maksymalnej możliwej wartości dla danego typu
i rozszerzeniem zakresu pytania na wszystkie typy całkowite bez znaku :
Przypisanie -1 działa dla dowolnego typu liczby całkowitej bez znaku (unsigned int, uint8_t, uint16_t itp.) Zarówno dla C, jak i C ++.
Alternatywnie, dla C ++ możesz:
<limits>
i używajstd::numeric_limits< your_type >::max()
Celem może być dodanie większej przejrzystości, ponieważ przypisanie -1
zawsze wymagałoby komentarza wyjaśniającego.
tak, pokazana reprezentacja jest bardzo poprawna, ponieważ jeśli zrobimy to w drugą stronę, u będzie wymagać od operatora odwrócenia wszystkich bitów, ale w tym przypadku logika jest dość prosta, jeśli weźmiemy pod uwagę rozmiar liczb całkowitych w maszynie
na przykład w większości maszyn liczba całkowita to 2 bajty = 16 bitów maksymalna wartość, jaką może przechowywać, to 2 ^ 16-1 = 65535 2 ^ 16 = 65536
0% 65536 = 0-1% 65536 = 65535, co odpowiada 1111 ............. 1 i wszystkie bity są ustawione na 1 (jeśli weźmiemy pod uwagę klasy reszt mod 65536), stąd jest dużo bezpośredni.
zgaduję
nie, jeśli weźmiesz pod uwagę tę koncepcję, jest idealna na obiady bez znaku i faktycznie działa
po prostu sprawdź następujący fragment programu
int main () {
unsigned int a=2;
cout<<(unsigned int)pow(double(a),double(sizeof(a)*8));
unsigned int b=-1;
cout<<"\n"<<b;
getchar();
return 0;
}
odpowiedź dla b = 4294967295 whcih to -1% 2 ^ 32 na 4-bajtowych liczbach całkowitych
stąd jest to doskonale poprawne dla liczb całkowitych bez znaku
w przypadku jakichkolwiek rozbieżności zgłoś plzz