Якщо номер занадто великий, він перекинеться на наступну пам'ять?

Я переглядав програмування C, і є лише кілька речей, які мене турбують.

Візьмемо, наприклад, цей код:

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);

Я знаю, що int може містити максимальне значення позитивного 2,147,483,647. Отже, переходячи на це, це "переливається" на наступну адресу пам'яті, що змушує елемент 2 з'являтися як "-2147483648" за цією адресою? Але це насправді не має сенсу, тому що на виході він все ще говорить, що наступна адреса містить значення 4, потім 5. Якщо номер перекинувся на наступну адресу, то це не змінить значення, що зберігається на цій адресі. ?

Я смутно пам'ятаю від програмування в MIPS Асамблеї і спостерігаю за зміною значень адрес у програмі крок за кроком, щоб значення, призначені для цих адрес, змінилися.

Якщо я не пам'ятаю неправильно, то тут виникає ще одне питання: якщо номер, присвоєний конкретній адресі, більше, ніж тип (як у myArray [2]), то це не впливає на значення, що зберігаються на наступному адресу?

Приклад: Ми маємо int = 4 млрд. На адресу 0x10010000. Звичайно, myNum не може зберігати 4 мільярди, тому він з'являється як деяке негативне число на цій адресі. Незважаючи на те, що він не може зберігати це велике число, він не впливає на значення, що зберігається на наступному адресі 0x10010004. Правильно?

Адреси пам'яті просто мають достатньо простору для утримання певних розмірів чисел/символів, і якщо розмір перевищує ліміт, то він буде представлений по-різному (наприклад, спроба зберегти 4 мільярди в int, але вона з'явиться як від'ємне число) і тому він не впливає на номери/символи, збережені на наступній адресі.

Вибачте, якщо я пішов за борт. Я маю головний пердеть мозку весь день з цього.

30
Якщо вам потрібні дійсно величезні числа, можна мати представлення числа, яке збільшує кількість пам'яті, яку вона використовує для розміщення великих чисел. Сам процесор не може це зробити, і це не є особливістю мови С, але бібліотека може її реалізувати - загальна бібліотека С є Арифметична бібліотека GNU Multiple Precision . Бібліотека має керувати пам'яттю для зберігання чисел, які мають вартість продуктивності поверх арифметики. Багато мов вбудовано (що не дозволяє уникнути витрат).
додано Автор Ken Gentle, джерело
@JonH: Проблема полягає в тому, що переповнення в Undefined Behavior. Компілятор C може виявити цей код і вивести, що його недоступний код , оскільки він безумовно переповнюється. Оскільки недоступний код не має значення, його можна усунути. Кінцевий результат: не залишився код.
додано Автор Calum, джерело
@OlivierDulac: Компілятор може виключити код, який звичайно переповнений. У вашому прикладі немає переповнення для входів 0-10. Але майте на увазі, що компілятор напевно знає, що user_inputed_value <= 10 .
додано Автор Calum, джерело
@ OlivierDulac: Насправді, компілятор може подивитися на вхідну процедуру, побачити, що він повторно сканує для цифр, і розгорнути цей цикл, щоб подивитися тільки на останні дві цифри. Ось чому деякі люди кажуть, що невизначене поведінка може відправитися назад у часі. Це потенційне переповнення може вплинути на код, що веде до нього.
додано Автор Calum, джерело
@MSalters: чи можна його обійти за допомогою c = INT.MAXINT - 10; c + = user_inputed_value; ? Що може вирішити компілятор? Хіба це не може просто дозволити операції (і під час виконання, то переповнення може бути з введеним значенням> 10)?
додано Автор moff, джерело
@MSalters: компілятор може припустити , що (і, наприклад, резервувати лише 1 байт (у user_inputed_value це ціле число) до старого, що значення припускає (неправильно) не потрібно більше), але не може знати напевно , якщо він також не змінює весь код, який читає user_inputed_value, який він не повинен (/ не може). Таким чином, він повинен дозволити примусити переповнення, ввівши число> 10.
додано Автор moff, джерело
@MSalters цікаві!
додано Автор moff, джерело
Домашнє завдання: Змініть простий процесор так, щоб він зробив розливу. Ви побачите, що логіка стає набагато складнішою, і все це пов'язано з "функцією", яка б гарантувала отвори безпеки всюди, не будучи корисними.
додано Автор stevensagaar, джерело
напишіть простий тест, я не програміст C, але щось за лінією int c = INT.MAXINT; c + = 1; і подивитися, що сталося з c.
додано Автор Martin, джерело
Можливо, ви заплутуєтеся перевищенням рядків .
додано Автор Robbie Dee, джерело
@MSalters, не пов'язаний безпосередньо з темою, але компілятор може оптимізувати булевий вираз, який перевіряє, чи відбулося переповнення. (Не запитуйте мене, як я знаю!)
додано Автор james large, джерело

5 Відповіді

Ні, це не є. У C змінні мають фіксований набір адрес пам'яті для роботи. Якщо ви працюєте над системою з 4-байтовим ints , і ви встановите змінну int до 2,147,483,647 , а потім додайте 1 , змінна зазвичай містить -2147483648 . (На більшості систем. Поведінка фактично не визначена.) Жодні інші місця пам'яті не будуть змінені.

По суті, компілятор не дозволить вам призначити значення, яке є занадто великим для цього типу. Це призведе до помилки компілятора. Якщо ви примусите його до випадку, значення буде скорочено.

Розглядаючи побітовим способом, якщо тип може зберігати лише 8 біт, і ви намагаєтеся примусити значення 1010101010101 у ньому з випадком, ви отримаєте нижній 8 біт, або <�код > 01010101 .

У вашому прикладі, незалежно від того, що ви робите, щоб myArray [2] , myArray [3] буде містити '4'. Немає "переливу". Ви намагаєтеся покласти щось більше, ніж 4 байта, це просто відкине все на високому кінці, залишивши нижній 4 байта. У більшості систем це призведе до -2147483648 .

З практичної точки зору, ви просто хочете переконатися, що це ніколи не станеться. Такі переповнення часто призводять до важко розв'язаних дефектів. Іншими словами, якщо ви вважаєте, що всі ваші значення будуть в мільярдах, не використовуйте int .

48
додано
Якщо ви працюєте над системою з 4-байтовими ints, і ви встановлюєте змінну int до 2,147,483,647, а потім додаєте 1, змінна буде містити -2147483648. , це невизначена поведінка , тому вона може обертатися навколо або може зробити щось зовсім інше; Я бачив, як компілятори оптимізують перевірки на основі відсутності переповнення і отримали, наприклад, нескінченні петлі ...
додано Автор Owen, джерело
@hobbs: Проблема полягає в тому, що коли компілятори керують програмою через невизначене поведінку, насправді запуск програми дійсно призведе до несподіваної поведінки, порівнянної з перезаписом.
додано Автор Owen, джерело
@MatthieuM з точки зору мови , це правда. З точки зору виконання на даній системі, про що ми говоримо тут, це абсолютна нісенітниця.
додано Автор Krzysztof Klimonda, джерело
Вибачте, ви правильні. Я повинен був додати "зазвичай" там.
додано Автор Steven Burnap, джерело

Підписане ціле переповнення - це невизначена поведінка. Якщо це відбудеться, ваша програма буде недійсною. Компілятор не зобов'язаний перевіряти це для вас, тому він може генерувати виконуваний файл, який, здається, робить щось розумне, але немає ніякої гарантії, що він буде.

Однак ціле переповнення без знака чітко визначено. Вона обернеться за модулем UINT_MAX + 1. На пам'ять, не зайняту вашою змінною, це не вплине.

See also https://stackoverflow.com/q/18195715/951890

24
додано
Я вважаю, що не є у стандарті C, але я припускаю, що може бути реалізація, де звичайна двійкова арифметика не використовується для int . Запропонуйте їм скористатися кодом Грея або BCD або EBCDIC . не знаю, чому хтось буде розробляти апаратні засоби, щоб робити арифметику кодом Грея або EBCDIC, але, знову ж таки, я не знаю, чому хтось зробив би unsigned з двійковим файлом і не підписав int з чим-небудь 2 доповнення.
додано Автор Wells, джерело
Добре, стандарти іноді є анахронічними. Дивлячись на посилання SO, є один коментар, який безпосередньо вражає його: "Важливо відзначити, що в сучасному світі не існує жодної архітектури, що використовує арифметику, окрім доповнення 2. наприклад, PDP-1 є чистим історичним артефактом.
додано Автор Wells, джерело
переповнення цілочисельного числа так само добре визначено, як це перевищення цілого числа без знака. якщо слово має $ N $ біт, верхня межа переповненого цілого цілого числа дорівнює $$ 2 ^ {N-1} -1 $$ (де вона обертається до $ -2 ^ {N-1} $), тоді як верхня межа для переповнення беззнакового цілого - $$ 2 ^ N - 1 $$ (де вона обертається до $ 0 $). однакові механізми для складання і віднімання, однакові розміри діапазону чисел ($ 2 ^ N $), які можуть бути представлені. просто інша межа переповнення.
додано Автор Wells, джерело
@ robertbristow-johnson: Не відповідно до стандарту C.
додано Автор bkwdesign, джерело

Отже, тут є дві речі:

  • рівень мови: які семантики C
  • рівень машини: які семантики збірки/процесора, які ви використовуєте

На рівні мови:

У C:

  • переповнення та переповнення визначаються як арифметика за модулем для цілих чисел , тобто їх значення "цикли"
  • переповнення та перевантаження - це невизначене поведінка для підписаних цілих чисел, тому все може відбутися

Для тих, хто хотів би прикладу "що-небудь", я бачив:

for (int i = 0; i >= 0; i++) {
    ...
}

перетворюватися:

for (int i = 0; true; i++) {
    ...
}

і так, це легітимне перетворення.

Це означає, що дійсно існують потенційні ризики перезапису пам'яті на переповнення через деякі дивні перетворення компіляторів.

Примітка: у Clang або gcc використовуйте -fsanitize = undefined в Debug, щоб активувати Невизначене поведінка Sanitizer , який буде перервано під час переповнення/переповнення цілих чисел.

Або це означає, що ви можете перезаписати пам'ять, використовуючи результат операції для індексування (неперевіреного) в масив. Це, на жаль, набагато більш ймовірно, за відсутності виявлення підтоку/переповнення.

Примітка: у Clang або gcc використовуйте -fsanitize = address у розділі Debug, щоб активувати адресний дезінфікувальник , який буде перервано у доступі поза межами.


At the machine level:

Це дійсно залежить від інструкцій з монтажу та процесора, який ви використовуєте:

  • on x86, ADD will use 2-complement on overflow/underflow, and set the OF (Overflow Flag)
  • on the future Mill CPU, there will be 4 different overflow modes for Add:
    • Modulo: 2-complement modulo
    • Trap: a trap is generated, halting computation
    • Saturate: value gets stuck to min on underflow or max on overflow
    • Double Width: the result is generated in a double-width register

Зверніть увагу, що якщо речі відбуваються в регістрах або в пам'яті, то в жодному випадку CPU не перезаписує пам'ять на переповнення.

14
додано
Чи підписані останні три режими? (Не має значення для першого, оскільки це 2-х доповнення.)
додано Автор Shizam, джерело
@Deduplicator: Відповідно до Введення в програмування CPU заводу Модель існують різні коди операцій для додавання підписів і беззнакового додавання; Я сподіваюся, що обидва операційні коди будуть підтримувати 4 режими (і бути в змозі працювати на різних біт-ширини і скалярні/вектори). Знову ж таки, це обладнання для випарів;)
додано Автор Owen, джерело

Для подальшого відповіді @ StevenBurnap, причиною цього є те, як комп'ютери працюють на машинному рівні.

Ваш масив зберігається в пам'яті (наприклад, в ОЗП). Коли виконується арифметична операція, значення в пам'яті копіюється у вхідні регістри схеми, яка виконує арифметику (ALU: Арифметична логічна одиниця ), після чого операція здійснюється за даними у вхідних регістрах, виробляючи результат у вихідному регістрі. Цей результат потім копіюється назад у пам'ять за правильною адресою в пам'яті, залишаючи інші області пам'яті недоторканими.

4
додано

First (assuming C99 standard), you may want to include standard header and use some of the types defined there, notably int32_t which is exactly a 32 bits signed integer, or uint64_t which is exactly a 64 bits unsigned integer, and so on. You might want to use types like int_fast16_t for performance reasons.

Читайте відповіді інших користувачів, пояснюючи, що арифметика без знаку ніколи не розливається (або переповнюється) на сусідні місця пам'яті. Остерігайтеся невизначеної поведінки на підписаному переповненні.

Then, if you need to compute exactly huge integer numbers (e.g. you want to compute factorial of 1000 with all its 2568 digits in decimal), you want bigints a.k.a. arbitrary precision numbers (or bignums). Algorithms for efficient bigint arithmetic are very clever, and usually requires using specialized machine instructions (e.g. some add word with carry, if your processor has that). Hence I strongly recommend in that case to use some existing bigint library like GMPlib

4
додано