Czy użycie wartości -1 do ustawienia wszystkich bitów na wartość true jest bezpieczne?

134

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 0xfffffffflub ~0lepiej?

hiperlogika
źródło
1
Nie rozumiem, czy możesz wyjaśnić?
corazza
8
Myślę, że najważniejsze jest to, czy znaczenie kodu jest jasne. Chociaż -1zawsze 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 0xffffffffnie dla przenośności lub poprawności, ale dla przejrzystości.
Cam Jackson
@CamJackson komentarz nie jest, a każdy, kto pisząc kod w C, może znać sposób reprezentacji wartości.
Trasa Miles
Pytanie pierwotnie zostało poprawnie oznaczone jako C i C ++. Języki mogą się różnić, ponieważ C ++ ma propozycję, aby wymagać uzupełnienia do dwóch. To powiedziawszy, nie zmienia to faktu, że -1pozostaje przenośnym i wstecznie kompatybilnym rozwiązaniem dla obu języków, ale może wpłynąć na niektóre argumenty w innych odpowiedziach.
Adrian McCarthy

Odpowiedzi:

156

Polecam zrobić to dokładnie tak, jak pokazałeś, ponieważ jest to najprostsze rozwiązanie. Inicjalizacja, -1któ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ść unsignedtypu.

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 longma 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 ~0wszystkie bity muszą być odwrócone. Odwrócenie da wynik -1na maszynie z dopełnieniem do dwóch (jest to wartość, której potrzebujemy!), Ale nie przyniesie -1innego wyniku. Na maszynie z dopełnieniem do jedynki daje zero. Zatem na maszynie z dopełnieniem jedynki powyższe zostanie zainicjalizowane ado 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ć ado najwyższej możliwej wartości, to -1lub UINT_MAX. Drugi będzie zależał od typu a- będziesz musiał użyć ULONG_MAXdo 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 -1ma wszystkie bity jednego (to nie zawsze musi). I nie mówimy o tym, czy ~0ma wszystkie bity jeden (oczywiście ma).

Ale mówimy o tym, jaki jest wynik zainicjowanej flagszmiennej. I do tego będzie działać tylko-1 z każdym typem i maszyną.

Johannes Schaub - litb
źródło
10
dlaczego gwarantuje się, że -1 zostanie zamienione na wszystkie? Czy to gwarantuje norma?
jalf
9
zachodząca konwersja polega na tym, że wielokrotnie dodaje o jeden więcej niż ULONG_MAX, aż znajdzie się w zakresie (6.3.1.3 w wersji roboczej C TC2). W C ++ jest tak samo, po prostu używając innego sposobu sformalizowania tego (modulo 2 ^ n). Wszystko sprowadza się do relacji matematycznych.
Johannes Schaub - litb
6
@litb: rzutowanie -1 jest z pewnością dobrym sposobem na uzyskanie maksymalnych wartości bez znaku, ale nie jest tak naprawdę opisowe; to jest powód, dla którego istnieją stałe _MAX (SIZE_MAX został dodany w C99); oczywiście, wersja C ++ numeric_limits<size_t>::max()jest trochę rozwlekła, ale obsada też ...
Christoph
12
„Nie mówimy o tym, czy -1 ma wszystkie bity jeden (nie zawsze ma). I nie mówimy o tym, czy ~ 0 ma wszystkie bity jeden (oczywiście ma)”. - co ??? Myślałem, że chodziło o ustawienie wszystkich bitów na 1. Tak działają flagi… nie? Patrzysz na kawałki . Kogo obchodzi wartość?
mpen
14
@Zaznacz pytanie, które interesuje. Pyta „Czy bezpiecznie jest użyć -1, aby ustawić wszystkie bity na prawdę”. To nie pyta o to, jakie bity -1są reprezentowane, ani nie pyta, jakie bity ~0mają. 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 ~0może nie być -1, ale jest to wartość, którą trzeba. Zobacz moją odpowiedź i podsumowanie @ Dingo.
Johannes Schaub - litb
51
  • 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.

Dingo
źródło
10
W jaki sposób ~ 0 (tj. Operator dopełnienia jedynki) polega na reprezentacji dopełnienia do dwóch?
Drew Hall
11
Masz to od tyłu. Jego flagi ustawiają na -1, co opiera się na reprezentacji dopełnienia dwójek. W reprezentacji znak + wielkość minus jeden ma ustawione tylko dwa bity: bit znaku i najmniej znaczący bit wielkości.
Stephen C. Steel
15
Standard C wymaga, aby wartość int równa zero miała swój bit znaku, a wszystkie bity wartości były równe zero. Po uzupełnieniu jednego, wszystkie te bity są jednym. Wartości int z ustawionymi wszystkimi bitami to: Znak i wielkość: INT_MIN Uzupełnienie do jednego: -0 Uzupełnienie do dwóch: -1 Zatem instrukcja "unsigned int flags = ~ 0;" przypisze dowolną powyższą wartość, która pasuje do reprezentacji liczby całkowitej platformy. Ale „-1” uzupełnienia do dwóch jest jedynym, który ustawi wszystkie bity flagi na jeden.
Dingo
9
@Stephen: Zgoda na reprezentację. Ale kiedy wartość int jest przypisana do unsigned int, unsigned nie uzyskuje swojej wartości przez przyjęcie wewnętrznej reprezentacji wartości int (z wyjątkiem systemów z dopełnieniem do dwóch, w których to generalnie działa). Wszystkie wartości przypisane do unsigned int są modulo (UINT_MAX + 1), więc przypisanie -1 działa bez względu na wewnętrzną reprezentację.
Dingo
20
@Mark: mylisz dwie operacje. ~0daje intoczywiście wartość z ustawionymi wszystkimi bitami. Ale przypisania intdo unsigned intnie 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 intwartości ujemnej do unsigned intwynikó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.
Steve Jessop,
25

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.

Doug T.
źródło
Istnieje wiele przypadków, w których nie obchodzi Cię to tak bardzo, ale są rzadkie. Np. Algorytmy działające na zestawach danych o długości N bitów, dzieląc je na fragmenty o rozmiarze (bez znaku) * CHAR_BIT bitów.
MSalters
2
+1. Nawet jeśli rozmiar typu danych jest większy niż liczba F (tj. Nie ustawiłeś wszystkich bitów na true), ponieważ jawnie ustawiasz wartość, masz przynajmniej świadomość, które bity są „bezpieczne użyć „..
mpen
17

Aby uniknąć wymienionych problemów, wystarczy wykonać następujące czynności:

unsigned int flags = 0;
flags = ~flags;

Przenośne i na temat.

hammar
źródło
2
Ale wtedy tracisz możliwość zadeklarowania flagsjako const.
David Stone
1
@DavidStoneunsigned int const flags = ~0u;
@Zoidberg '- To nie działa w systemach innych niż dwa uzupełnienia. Na przykład w systemie wielkości znaku ~0jest liczbą całkowitą, która ma wszystkie bity ustawione na 1, ale kiedy następnie przypisujesz intją do unsignedzmiennej 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.
David Stone,
Jest to również powód, dla którego ta odpowiedź jest niebezpieczna. Wydaje się, że odpowiedź @Zoidberga byłaby identyczna, ale tak nie jest. Jednak jako osoba czytająca kod musiałbym o tym pomyśleć, aby zrozumieć, dlaczego wykonałeś dwa kroki, aby go zainicjować, i być może kusiło mnie, aby zmienić to na jeden krok.
David Stone
2
Ach tak, nie zauważyłem uprzyrostka w Twojej odpowiedzi. To oczywiście zadziałałoby, ale nadal występuje problem z określeniem używanego typu danych ( unsignedi 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.
David Stone
13

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ż 0xffffffffzakłada, że ​​unsigned int jest 32-bitową liczbą całkowitą.

Edouard A.
źródło
Podoba mi się to ze względu na jego standard, ale jest zbyt rozwlekły i sprawia, że ​​dwukrotnie podaje się typ. Używanie ~ 0 jest prawdopodobnie bezpieczniejsze, ponieważ 0 może być dowolną liczbą całkowitą. (Chociaż jestem świadomy, że pachnie zbyt mocno C.)
Macke
Fakt, że jest rozwlekły, można uznać za zaletę. Ale ja też lubię ~ 0.
Edouard A.
2
Możesz złagodzić tępotę za pomocą standardowego makra UINT_MAX, ponieważ i tak kodujesz typ unsigned int.
2
@Macke Możesz uniknąć podawania typu w C ++ 11 za pomocą auto. auto const flags = std::numeric_limit<unsigned>::max().
David Stone
11
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ć, autoaby 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.

Adrian McCarthy
źródło
10

Konwersja -1 na dowolny typ bez znaku jest gwarantowana przez standard w celu uzyskania wszystkich jedynek. Używanie of ~0Ujest ogólnie złe, ponieważ 0ma typ unsigned inti nie wypełni wszystkich bitów większego typu bez znaku, chyba że wyraźnie napiszesz coś takiego ~0ULL. W rozsądnych systemach ~0powinien 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ć, 0xffffffffjeś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ć.

R .. GitHub PRZESTAŃ POMÓC LODOWI
źródło
FWIW, jeśli mam na myśli „wszystkie 1 bity”, których używam ~(type)0(no cóż, wypełnij typeoczywiś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.
Donal Fellows
6
@Donal: po prostu się mylisz. C określa, że ​​podczas konwersji wartości, która nie pasuje do typu bez znaku, wartości są zmniejszane modulo 2 ^ n, gdzie n jest liczbą bitów w typie docelowym. Dotyczy to zarówno wartości ze znakiem, jak i większych typów bez znaku. Nie ma to nic wspólnego z uzupełnianiem się dwóch.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
2
Oni zrobić wdrożyć ten sam, który jest trywialny; po prostu używasz niepodpisanego kodu operacji odejmowania zamiast podpisanego lub neginstrukcji. 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.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
1
@R .: var = ~(0*var)Sprawa zakończy się niepowodzeniem, jeśli vartyp bez znaku będzie węższy niż int. Może var = ~(0U*var)? (osobiście nadal wolę -1).
kawiarnia
1
Ta odpowiedź jest znacznie jaśniejsza niż odpowiedź Johannesa Schauba. Jednak przypisanie ujemnej liczby całkowitej literału do typu bez znaku bez rzutowania zwykle powoduje ostrzeżenie kompilatora. Zniesienie ostrzeżenia wymaga rzutowania, co oznacza, że ​​i tak trzeba zwracać uwagę na typ celu, więc równie dobrze możesz użyć UINT_MAX lub ULONG_MAX i być wyraźnym, zamiast polegać na drobnych szczegółach w standardzie, co wyraźnie dezorientuje wielu innych programistów .
Adrian McCarthy
5

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.

Michael Norrish
źródło
1
sugerowane stałe są w rzeczywistości zdefiniowane w plikach limits.h - stdint.h zawiera ograniczenia dla dodatkowych typów liczb całkowitych (liczby całkowite o stałym rozmiarze, intptr_t, ...)
Christoph
5

Tak. Jak wspomniano w innych odpowiedziach, -1jest 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;
Diamond Python
źródło
4
Jako programista C, taki kod przyprawia mnie o złe sny w nocy.
jforberg
A co się stanie, gdy użyjesz tego w kontekście takim, jak ALL_BITS_TRUE ^ agdzie ajest 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.
Peter Cordes,
Nie, ALL_BITS_TRUE ^ apowoduje błąd kompilacji, ponieważ ALL_BITS_TRUEjest niejednoznaczny. Można go jednak użyć w uint32_t(ALL_BITS_TRUE) ^ aten 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, operatoraby upewnić się, że użytkownicy nie próbują używać int(ALL_BITS_TRUE). Zaktualizuję odpowiedź.
Diamond Python,
3

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_trodzaje.

myron-semack
źródło
2

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 znakirozszerzane 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ź.

Nathan Fellman
źródło
1
Gratulacje, znalazłeś błąd kompilatora!
tc.
Czy zostało to gdzieś udokumentowane jako błąd?
Nathan Fellman
1
Zakładając, że 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 0xffffffffto 4294967295 (a nie 36893488147419103231) i nie widać żadnych konwersji do typów całkowitych ze znakiem.
tc.
2

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.

James Hopkin
źródło
Ogólnie rzecz biorąc, nie możemy być pewni wartości bitów wypełnienia. A jeśli chcemy, możemy być w niebezpieczeństwie, ponieważ moglibyśmy wygenerować dla nich reprezentację pułapki (i może ona podnieść sygnały). Jednak std wymaga, aby znak bez znaku nie miał bitów dopełnienia, aw 4.7 / 2 w standardzie c ++ mówi, że konwersja liczby całkowitej na typ bez znaku, wartość wynikowej zmiennej bez znaku jest najmniejszą wartością zgodną ze źródłową wartością całkowitą, (modulo 2 ^ n, n == liczba bitów typu bez znaku). następnie (-1) == ((2 ^ n) -1) (mod 2 ^ n). 2 ^ n-1 ma wszystkie bity ustawione w czystym binarnym systemie liczbowym.
Johannes Schaub - litb
Jeśli naprawdę chcemy mieć wszystkie bity 1 w reprezentacji obiektowej typu bez znaku, potrzebowalibyśmy memset. Ale możemy w ten sposób wygenerować reprezentację pułapki :( W każdym razie implementacja prawdopodobnie nie ma powodu, aby odrzucać trochę swoich liczb całkowitych bez znaku, więc użyje jej do przechowywania swoich wartości. Ale masz bardzo dobry punkt - nic nie stoi na przeszkodzie interpretacja posiadania kilku głupich, nonsensownych bitów, jak sądzę (poza char / signed char / unsigned char, który nie może ich mieć). +1 oczywiście :)
Johannes Schaub - litb
W końcu myślę, że norma mogłaby być jaśniejsza, do jakiej reprezentacji się odnosi w 4.7 / 2. Jeśli odnosi się do reprezentacji obiektu, to nie ma już miejsca na wypełnianie bitów (widziałem ludzi kłócących się w ten sposób, w czym nie widzę nic złego). Ale myślę, że mówi o reprezentacji wartości (ponieważ wszystko w 4.7 / 2 i tak dotyczy wartości - a następnie bity wypełniające mogą zagnieżdżać się obok bitów wartości.
Johannes Schaub - litb
1
Wydaje się, że norma dość wyraźnie ma na myśli reprezentacje „uzupełnienia do 2, uzupełnienia 1 i ze znakiem”, ale nie chce niczego wykluczać. Ciekawy punkt na temat zalewkowania reprezentacji. O ile wiem, bit, który zacytowałem, to definicja `` czystego binarnego systemu numeracji '' w odniesieniu do standardu - bit `` z wyjątkiem '' na końcu jest tak naprawdę moją jedyną wątpliwością, czy rzutowanie -1 gwarantuje praca.
James Hopkin
2

Chociaż 0xFFFF(lub 0xFFFFFFFFitp.) 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 typedefmaskę 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 intma 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ą, intbę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 intzostanie 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 longbez rozszerzenia znaku.

supercat
źródło
1

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.

Drew Hall
źródło
6
Ja też to powiedziałem. Ale potem zdałem sobie sprawę, że się myliłem, ponieważ -1 ze znakiem -1 zawsze konwertuje się na max_value bez znaku zgodnie z regułami konwersji, niezależnie od reprezentacji wartości. Przynajmniej tak jest w C ++, nie mam pod ręką standardu C.
Steve Jessop
6
istnieje niejasność. -1 to nie 0xFFFFFFFF. Ale -1 to 0xFFFFFFFF, jeśli zostanie przekonwertowany na int bez znaku (mający 32 bity). Myślę, że właśnie dlatego ta dyskusja jest tak trudna. Wiele osób ma na myśli bardzo różne rzeczy, kiedy mówi o tych strunach bitowych.
Johannes Schaub - litb
1

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::bitsetze wszystkimi bitami ustawionymi na 1 z tych samych powodów, co w innych odpowiedziach.

David Stone
źródło
0

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.

Zifre
źródło
4
„0xFF… nie jest dobre, ponieważ zależy to od szerokości czcionki”. Myślę, że jest to jedyna rozsądna droga. Powinieneś jasno zdefiniować, co oznacza każda flaga / bit w twoim programie. Tak więc, jeśli zdefiniujesz, że używasz najniższych 32 bitów do przechowywania flag, powinieneś ograniczyć się do używania tych 32 bitów, niezależnie od tego, czy rzeczywisty rozmiar int to 32 czy 64.
Juan Pablo Califano
0

Mówię:

int x;
memset(&x, 0xFF, sizeof(int));

To zawsze da pożądany rezultat.

Alex
źródło
4
Nie w systemach z 9-bitowymi znakami!
tc.
0

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:

  1. Dołącz <limits>i używajstd::numeric_limits< your_type >::max()
  2. Napisz niestandardową funkcję opartą na szablonie (pozwoliłoby to również na sprawdzenie poprawności, tj. Jeśli typ docelowy jest naprawdę typem bez znaku)

Celem może być dodanie większej przejrzystości, ponieważ przypisanie -1zawsze wymagałoby komentarza wyjaśniającego.

Antonio
źródło
0

Sposób, aby znaczenie było nieco bardziej oczywiste, a jednocześnie uniknąć powtarzania typu:

const auto flags = static_cast<unsigned int>(-1);
FlorianH
źródło
-6

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

ankit sablok
źródło
9
Dwie uwagi: po pierwsze, bardzo się mylisz co do wielkości liczb całkowitych na „większości” maszyn. Po drugie, ur txt jest bardzo trudny do przeczytania 2 z powodu sumy języka seckrit. Proszę nas po angielsku .
Konrad Rudolph