Przeglądałem programowanie w C i przeszkadza mi tylko kilka rzeczy.
Weźmy na przykład ten kod:
int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);
Wiem, że int może pomieścić maksymalną wartość dodatnią 2 147 483 647. A zatem, przechodząc o jeden powyżej, „rozlewa się” na następny adres pamięci, co powoduje, że element 2 pojawia się pod tym adresem jako „-2147483648”? Ale to naprawdę nie ma sensu, ponieważ na wyjściu nadal jest napisane, że następny adres zawiera wartość 4, a następnie 5. Jeśli liczba przelałaby się na następny adres, to nie zmieniłoby to wartości zapisanej pod tym adresem ?
Nie pamiętam z programowania w MIPS Assembly i patrzenia, jak adresy zmieniają się podczas programu krok po kroku, że wartości przypisane do tych adresów zmieniają się.
O ile nie pamiętam niepoprawnie, oto kolejne pytanie: Jeśli liczba przypisana do określonego adresu jest większa niż typ (jak w myArray [2]), to czy nie wpływa to na wartości przechowywane pod kolejnym adresem?
Przykład: Mamy int myNum = 4 miliardy pod adresem 0x10010000. Oczywiście myNum nie może przechowywać 4 miliardów, więc pojawia się jako pewna liczba ujemna pod tym adresem. Mimo że nie jest w stanie zapisać tak dużej liczby, nie ma to wpływu na wartość przechowywaną pod kolejnym adresem 0x10010004. Poprawny?
Adresy pamięci mają po prostu wystarczającą ilość miejsca, aby pomieścić pewne rozmiary liczb / znaków, a jeśli rozmiar przekroczy limit, wówczas będzie reprezentowany inaczej (jak próba zapisania 4 miliardów w int, ale pojawi się jako liczba ujemna) i więc nie ma to wpływu na liczby / znaki przechowywane pod następnym adresem.
Przepraszam, jeśli poszedłem za burtę. Cały dzień miałem od tego pierdnięcie mózgu.
źródło
int c = INT.MAXINT; c+=1;
i zobacz, co się stało z c.Odpowiedzi:
Nie. W C zmienne mają ustalony zestaw adresów pamięci do pracy. Jeśli pracujesz w systemie z 4 bajtami
ints
i ustawiszint
zmienną na,2,147,483,647
a następnie dodasz1
, zmienna zwykle będzie zawierać-2147483648
. (W większości systemów. Zachowanie jest w rzeczywistości niezdefiniowane.) Żadne inne lokalizacje pamięci nie zostaną zmodyfikowane.Zasadniczo kompilator nie pozwoli ci przypisać wartości, która jest zbyt duża dla tego typu. To wygeneruje błąd kompilatora. Jeśli wymusisz to w przypadku, wartość zostanie obcięta.
Patrząc nieco bitowo, jeśli typ może przechowywać tylko 8 bitów i spróbujesz zmusić wartość
1010101010101
do niego za pomocą skrzynki, skończysz z dolnymi 8 bitami lub01010101
.W twoim przykładzie, niezależnie od tego, co zrobisz
myArray[2]
,myArray[3]
będzie zawierać „4”. Nie ma „przelewu”. Próbujesz umieścić coś, co ma więcej niż 4 bajty, po prostu odetnie wszystko z wyższej półki, pozostawiając dolne 4 bajty. W większości systemów spowoduje to-2147483648
.Z praktycznego punktu widzenia chcesz po prostu upewnić się, że tak się nigdy nie stanie. Tego rodzaju przepełnienia często powodują trudne do rozwiązania wady. Innymi słowy, jeśli uważasz, że istnieje jakakolwiek szansa, że twoje wartości będą w miliardach, nie używaj
int
.źródło
Przepełnienie całkowitą ze znakiem jest zachowaniem niezdefiniowanym. Jeśli tak się stanie, twój program jest nieprawidłowy. Kompilator nie musi tego sprawdzać, więc może wygenerować plik wykonywalny, który wydaje się robić coś rozsądnego, ale nie ma gwarancji, że to zrobi.
Jednak przepełnienie liczb całkowitych bez znaku jest dobrze zdefiniowane. To zawinie modulo UINT_MAX + 1. Nie wpłynie to na pamięć nie zajmowaną przez zmienną.
Zobacz także https://stackoverflow.com/q/18195715/951890
źródło
int
. I s'pose mogliby wykorzystać kod Grey lub BCD lub EBCDIC . Nie wiem, dlaczego ktokolwiek miałby projektować sprzęt do arytmetyki z kodem Graya lub EBCDIC, ale z drugiej strony, nie wiem, dlaczego ktokolwiek zrobiłbyunsigned
z binarnym i podpisywałbyint
się czymkolwiek innym niż uzupełnienie 2.Istnieją więc dwie rzeczy:
Na poziomie językowym:
W C:
Dla tych, którzy chcieliby przykładu „cokolwiek”, widziałem:
zmienić się w:
i tak, jest to uzasadniona transformacja.
Oznacza to, że rzeczywiście istnieje potencjalne ryzyko zastąpienia pamięci przy przepełnieniu z powodu dziwnej transformacji kompilatora.
Uwaga: na Clang lub gcc użyj
-fsanitize=undefined
w Debugowaniu, aby aktywować narzędzie do dezynfekcji niezdefiniowanych zachowań, które przerwie działanie przy niedopełnieniu / przepełnieniu podpisanych liczb całkowitych.Lub oznacza to, że można zastąpić pamięć, używając wyniku operacji do indeksowania (niezaznaczonego) do tablicy. Jest to niestety o wiele bardziej prawdopodobne w przypadku braku wykrywania niedopełnienia / przepełnienia.
Uwaga: na Clang lub gcc użyj
-fsanitize=address
w Debugowaniu, aby aktywować narzędzie Sanitizer adresu, które przerwie dostęp poza granicami.Na poziomie maszyny :
To zależy od instrukcji montażu i używanego procesora:
Add
:Zauważ, że niezależnie od tego, czy coś dzieje się w rejestrach, czy w pamięci, w żadnym przypadku procesor nie zastępuje pamięci po przepełnieniu.
źródło
Aby udzielić odpowiedzi @ StevenBurnap, dzieje się tak z powodu sposobu działania komputerów na poziomie komputera.
Twoja tablica jest przechowywana w pamięci (np. W pamięci RAM). Gdy wykonywana jest operacja arytmetyczna, wartość z pamięci jest kopiowana do rejestrów wejściowych obwodu, który wykonuje arytmetykę (ALU: Arithmetic Logic Unit ), następnie operacja jest wykonywana na danych w rejestrach wejściowych, co daje wynik w rejestrze wyjściowym. Wynik ten jest następnie kopiowany z powrotem do pamięci pod prawidłowym adresem w pamięci, pozostawiając pozostałe obszary pamięci nietknięte.
źródło
Po pierwsze (zakładając standard C99), możesz chcieć dołączyć
<stdint.h>
standardowy nagłówek i użyć niektórych typów tam zdefiniowanych, w szczególności,int32_t
która jest dokładnie 32-bitową liczbą całkowitą zeuint64_t
znakiem lub dokładnie dokładnie 64-bitową liczbą całkowitą bez znaku i tak dalej. Możesz chcieć używać typów takich jakint_fast16_t
ze względu na wydajność.Przeczytaj inne odpowiedzi wyjaśniające, że arytmetyka bez znaku nigdy nie rozlewa (ani nie przepełnia) do sąsiednich miejsc pamięci. Uważaj na niezdefiniowane zachowanie przy podpisanym przepełnieniu.
Następnie, jeśli chcesz obliczyć dokładnie ogromne liczby całkowite (np. Chcesz obliczyć silnię 1000 ze wszystkimi 2568 cyframi dziesiętnymi), potrzebujesz bigintów, czyli dowolnych liczb precyzji (lub bignów). Algorytmy efektywnej arytmetyki bigint są bardzo sprytne i zwykle wymagają użycia specjalistycznych instrukcji maszynowych (np. Niektóre dodają słowo z przeniesieniem, jeśli procesor to posiada). Dlatego zdecydowanie zalecam w takim przypadku użycie istniejącej biblioteki bigint, takiej jak GMPlib
źródło