Krótka odpowiedź
Twoje i
zostanie przekonwertowane na liczbę całkowitą bez znaku przez dodanie UINT_MAX + 1
, a następnie dodawanie zostanie przeprowadzone z wartościami bez znaku, co spowoduje duże result
(w zależności od wartości u
i i
).
Długa odpowiedź
Zgodnie ze standardem C99:
6.3.1.8 Zwykłe konwersje arytmetyczne
- Jeśli oba operandy mają ten sam typ, dalsza konwersja nie jest potrzebna.
- W przeciwnym razie, jeśli oba operandy mają typy liczb całkowitych ze znakiem lub oba mają typy całkowite bez znaku, operand o typie mniejszej liczby całkowitej konwersji jest konwertowany na typ operandu z wyższą rangą.
- W przeciwnym razie, jeśli operand, który ma typ liczby całkowitej bez znaku, ma rangę większą lub równą randze typu drugiego operandu, operand z typem liczby całkowitej ze znakiem jest konwertowany na typ operandu z typem liczby całkowitej bez znaku.
- W przeciwnym razie, jeśli typ operandu z typem liczby całkowitej ze znakiem może reprezentować wszystkie wartości typu operandu z typem liczby całkowitej bez znaku, wówczas operand z typem liczby całkowitej bez znaku jest konwertowany na typ operandu z typem liczby całkowitej ze znakiem.
- W przeciwnym razie oba operandy są konwertowane na typ liczby całkowitej bez znaku odpowiadającego typowi operandu z typem liczby całkowitej ze znakiem.
W twoim przypadku mamy jeden unsigned int ( u
) i signed int ( i
). Odnosząc się do (3) powyżej, ponieważ oba operandy mają tę samą rangę, twoja i
wola musi zostać przekonwertowana na liczbę całkowitą bez znaku.
6.3.1.3 Liczby całkowite ze znakiem i bez znaku
- Gdy wartość o typie całkowitym jest konwertowana na inny typ liczby całkowitej inny niż _Bool, jeśli wartość może być reprezentowana przez nowy typ, pozostaje niezmieniona.
- W przeciwnym razie, jeśli nowy typ jest bez znaku, wartość jest konwertowana przez wielokrotne dodawanie lub odejmowanie o jedną wartość większą niż maksymalna wartość, która może być reprezentowana w nowym typie, dopóki wartość nie znajdzie się w zakresie nowego typu.
- W przeciwnym razie nowy typ jest podpisany i nie można w nim przedstawić wartości; wynik jest zdefiniowany w ramach implementacji lub generowany jest sygnał zdefiniowany w implementacji.
Teraz musimy odwołać się do (2) powyżej. Twoja i
zostanie przekonwertowana na wartość bez znaku przez dodanie UINT_MAX + 1
. Wynik będzie więc zależał od tego, jak UINT_MAX
zdefiniowano w Twojej implementacji. Będzie duży, ale się nie przepełni, bo:
6.2.5 (9)
Obliczenie obejmujące operandy bez znaku nigdy nie może się przepełnić, ponieważ wynik, którego nie można reprezentować przez wynikowy typ liczby całkowitej bez znaku, jest zmniejszany modulo liczba, która jest o jeden większa niż największa wartość, która może być reprezentowana przez wynikowy typ.
Bonus: Pół-WTF konwersji arytmetycznej
#include <stdio.h>
int main(void)
{
unsigned int plus_one = 1;
int minus_one = -1;
if(plus_one < minus_one)
printf("1 < -1");
else
printf("boring");
return 0;
}
Możesz użyć tego linku, aby wypróbować to online: https://repl.it/repls/QuickWhimsicalBytes
Bonus: Efekt uboczny konwersji arytmetycznej
Reguły konwersji arytmetycznej można wykorzystać do uzyskania wartości UINT_MAX
poprzez zainicjowanie wartości bez znaku -1
, np .:
unsigned int umax = -1; // umax set to UINT_MAX
Gwarantuje to przenośność niezależnie od podpisanej reprezentacji numeru systemu ze względu na opisane powyżej reguły konwersji. Zobacz to pytanie SO, aby uzyskać więcej informacji: Czy bezpieczne jest użycie -1 do ustawienia wszystkich bitów na true?
Konwersja ze podpisanego na niepodpisany niekoniecznie musi po prostu kopiować lub reinterpretować reprezentację podpisanej wartości. Cytując standard C (C99 6.3.1.3):
Dla reprezentacji dopełnienia tych dwóch, która jest obecnie prawie uniwersalna, reguły odpowiadają reinterpretacji bitów. Ale dla innych reprezentacji (znak i wielkość lub dopełnienie jedynki), implementacja C musi nadal zorganizować ten sam wynik, co oznacza, że konwersja nie może po prostu skopiować bitów. Na przykład (unsigned) -1 == UINT_MAX, niezależnie od reprezentacji.
Ogólnie konwersje w C są definiowane do działania na wartościach, a nie na reprezentacjach.
Aby odpowiedzieć na pierwotne pytanie:
Wartość i jest konwertowana na bez znaku int, co daje
UINT_MAX + 1 - 5678
. Ta wartość jest następnie dodawana do wartości 1234 bez znaku, otrzymującUINT_MAX + 1 - 4444
.(W przeciwieństwie do przepełnienia bez znaku, przepełnienie ze znakiem wywołuje niezdefiniowane zachowanie. Zawijanie jest powszechne, ale nie jest gwarantowane przez standard C - a optymalizacje kompilatora mogą siać spustoszenie w kodzie, który przyjmuje nieuzasadnione założenia).
źródło
Nawiązując do Biblii :
źródło
Kiedy dodawana jest jedna zmienna bez znaku i jedna ze znakiem ze znakiem (lub dowolna operacja binarna), obie są niejawnie konwertowane na bez znaku, co w tym przypadku dałoby ogromny wynik.
Jest więc bezpieczny w tym sensie, że wynik może być ogromny i błędny, ale nigdy się nie zawiedzie.
źródło
Podczas konwersji z podpisanego na niepodpisany istnieją dwie możliwości. Liczby, które były pierwotnie dodatnie, pozostają (lub są interpretowane jako) tę samą wartość. Liczba, która była pierwotnie ujemna, będzie teraz interpretowana jako większe liczby dodatnie.
źródło
Jak już wcześniej udzielono, możesz bez problemu przesyłać między podpisanymi i niepodpisanymi. Wielkość obramowania dla liczb całkowitych ze znakiem to -1 (0xFFFFFFFF). Spróbuj dodać i odjąć od tego, a przekonasz się, że możesz rzucić z powrotem i sprawdzić, czy jest poprawne.
Jeśli jednak zamierzasz przesyłać w tę iz powrotem, zdecydowanie radzę nazwać zmienne tak, aby było jasne, jakiego typu są, np .:
Zbyt łatwo jest się rozproszyć ważniejszymi sprawami i zapomnieć, która zmienna jest jakiego typu, jeśli zostaną nazwane bez podpowiedzi. Nie chcesz rzutować na niepodpisany, a następnie używać go jako indeksu tablicy.
źródło
i zostanie przekonwertowany na liczbę całkowitą bez znaku.
Bezpieczeństwo w sensie bycia dobrze zdefiniowanym tak (patrz https://stackoverflow.com/a/50632/5083516 ).
Reguły są zwykle napisane trudnymi do odczytania standardami, ale zasadniczo niezależnie od reprezentacji użytej w liczbie całkowitej ze znakiem, liczba całkowita bez znaku będzie zawierać uzupełnienie do 2 reprezentacji liczby.
Dodawanie, odejmowanie i mnożenie będą działać poprawnie na tych liczbach, dając w wyniku kolejną liczbę całkowitą bez znaku zawierającą liczbę uzupełnioną do dwóch reprezentującą „wynik rzeczywisty”.
dzielenie i rzutowanie na większe typy liczb całkowitych bez znaku da dobrze zdefiniowane wyniki, ale wyniki te nie będą reprezentacjami „rzeczywistego wyniku” z dopełnieniem 2.
Podczas gdy konwersje ze znakiem na bez znaku są zdefiniowane przez standard, odwrotność jest zdefiniowana przez implementację, zarówno gcc, jak i msvc definiują konwersję w taki sposób, że otrzymasz "rzeczywisty wynik" podczas konwersji liczby uzupełnienia do 2 przechowywanej w liczbie całkowitej bez znaku z powrotem na liczbę całkowitą ze znakiem . Spodziewam się, że znajdziesz inne zachowanie tylko w mało znanych systemach, które nie używają uzupełnień do 2 dla liczb całkowitych ze znakiem.
https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx
źródło
Straszne odpowiedzi w bród
Ozgur Ozcitak
To jest całkowicie błędne.
Mats Fredriksson
To też jest złe. Bez znaku int mogą być promowane do int, jeśli mają taką samą precyzję ze względu na wypełnienie bitów w typie bez znaku.
smh
Źle. Może tak, a może nie.
Źle. Jest to niezdefiniowane zachowanie, jeśli powoduje przepełnienie lub wartość zostaje zachowana.
Anonimowy
Źle. Zależy od dokładności int względem wartości int bez znaku.
Taylor Price
Źle. Próba zapisania wartości poza zakresem liczby całkowitej ze znakiem powoduje niezdefiniowane zachowanie.
Teraz mogę wreszcie odpowiedzieć na pytanie.
Jeśli dokładność int będzie równa unsigned int, u zostanie podniesione do int ze znakiem, a otrzymasz wartość -4444 z wyrażenia (u + i). Teraz, jeśli u i ja mamy inne wartości, możesz uzyskać przepełnienie i niezdefiniowane zachowanie, ale z tymi dokładnymi liczbami otrzymasz -4444 [1] . Ta wartość będzie miała typ int. Ale próbujesz zapisać tę wartość w unsigned int, aby następnie został rzutowany na int bez znaku, a wartość, którą otrzyma wynik, będzie (UINT_MAX + 1) - 4444.
Jeśli dokładność wartości typu unsigned int będzie większa niż liczba int, int ze znakiem zostanie podwyższona do liczby int bez znaku, dając wartość (UINT_MAX + 1) - 5678, która zostanie dodana do drugiej liczby int bez znaku 1234. Czy u i i inne wartości, które powodują, że wyrażenie wykracza poza zakres {0..UINT_MAX} wartość (UINT_MAX + 1) zostanie dodana lub odjęta, dopóki wynik NIE znajdzie się w zakresie {0..UINT_MAX) i nie wystąpi żadne niezdefiniowane zachowanie .
Co to jest precyzja?
Liczby całkowite mają bity wypełniające, bity znaku i bity wartości. Liczby całkowite bez znaku nie mają oczywiście bitu znaku. Ponadto gwarantuje się, że znak bez znaku nie będzie zawierał bitów wypełniających. Liczba bitów wartości, które ma liczba całkowita, określa jej dokładność.
[Gotchas]
Samo makro rozmiar makra nie może służyć do określenia dokładności liczby całkowitej, jeśli obecne są bity wypełniające. A rozmiar bajtu nie musi być oktetem (osiem bitów), jak określono w C99.
[1] Przelew może wystąpić w jednym z dwóch punktów. Albo przed dodaniem (podczas promocji) - gdy masz int bez znaku, który jest zbyt duży, aby zmieścić się w int. Przepełnienie może również wystąpić po dodaniu, nawet jeśli unsigned int znajdował się w zakresie int, po dodaniu wynik może nadal przepełniać.
źródło
int
są konwertowaneunsigned int
na zwykłe konwersje arytmetyczne.int
lubunsigned int
do jednego z tych typów, w których coś jest typuunsigned int
lubint
jest oczekiwane. W TC2 dodano „lub równe”, aby umożliwić wyliczone typy rang konwersji równeint
lubunsigned int
konwertowane na jeden z tych typów. Nigdy nie było zamierzone, aby opisywana promocja zmieniała się międzyunsigned int
aint
. Wspólne określanie typu międzyunsigned int
iint
jest nadal regulowane przez 6.3.1.8, nawet po TC2.