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?
c++
c
binary
bit-fields
hiperlogika
źródło
źródło
-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łbym0xffffffff
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 anunsigned int
, a następnie przypisze go doa
. To, co się dzieje, gdyunsigned 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 zainicjalizowanea
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
lubUINT_MAX
. Drugi będzie zależał od typua
- będziesz musiał użyćULONG_MAX
dounsigned 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ą.źródło
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.
źródło
~0
dajeint
oczywiście wartość z ustawionymi wszystkimi bitami. Ale przypisaniaint
dounsigned 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, przypisanieint
wartości ujemnej dounsigned 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.
źródło
Aby uniknąć wymienionych problemów, wystarczy wykonać następujące czynności:
unsigned int flags = 0; flags = ~flags;
Przenośne i na temat.
źródło
flags
jakoconst
.unsigned int const flags = ~0u;
~0
jest liczbą całkowitą, która ma wszystkie bity ustawione na 1, ale kiedy następnie przypisujeszint
ją dounsigned
zmiennejflags
, 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ą.źródło
auto
.auto const flags = std::numeric_limit<unsigned>::max()
.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.
źródło
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 typunsigned 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 dopuszczalneUINT_MAX
,ULONG_MAX
,ULLONG_MAX
, itd.Osobiście zawsze używam -1. To zawsze działa i nie musisz o tym myśleć.
źródło
~(type)0
(no cóż, wypełnijtype
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ślivar
typ bez znaku będzie węższy niżint
. Możevar = ~(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.
źródło
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;
źródło
ALL_BITS_TRUE ^ a
gdziea
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ć wuint32_t(ALL_BITS_TRUE) ^ a
ten sposób. Możesz spróbować samemu na cpp.sh :) Obecnie dodałbymstatic_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.źródło
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:
Poniższe umieszcza 64 jedynki w RAX:
Aby uzyskać kompletność, poniższe umieszczenie 32 1s w dolnej części RAX (aka EAX):
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ź.
źródło
UINT64_C(0xffffffff)
rozwija się do czegoś podobnego0xffffffffuLL
, jest to zdecydowanie błąd kompilatora. Standard C w dużej mierze omawia wartości , wartość reprezentowana przez0xffffffff
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):
To pozostawia możliwość, że jeden z bitów będzie w ogóle cokolwiek.
źródło
Chociaż
0xFFFF
(lub0xFFFFFFFF
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 elemencieint
, ale będzie pasować dounsigned int
. Jeśli anint
ma 16 bitów, tolongvar &= ~0x4000;
lublongvar &= ~0x10000
; wyczyści jeden bitlongvar
, alelongvar &= ~0x8000;
usunie bit 15 i wszystkie bity powyżej. Wartości, które pasują,int
będą miały operator uzupełnienia zastosowany do typuint
, ale wynik zostanie rozszerzony do znakulong
, ustawiając górne bity. W przypadku wartości, które są zbyt duże, do typuunsigned int
zostanie zastosowany operator uzupełnienialong
. Wartości znajdujące się między tymi rozmiarami będą jednak stosować operator dopełniacza do typuunsigned int
, który zostanie następnie przekonwertowany na typlong
bez rozszerzenia znaku.źródło
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.
źródło
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.źródło
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.źródło
Mówię:
int x; memset(&x, 0xFF, sizeof(int));
To zawsze da pożądany rezultat.
źródło
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.źródło
Sposób, aby znaczenie było nieco bardziej oczywiste, a jednocześnie uniknąć powtarzania typu:
const auto flags = static_cast<unsigned int>(-1);
źródło
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
źródło