Я изучал программирование на Си, и меня беспокоит всего пара вещей.
Давайте возьмем этот код для примера:
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 Assembly и наблюдения за изменением значений адресов во время программы, шаг за шагом, что значения, назначенные этим адресам, будут меняться.
Если я не помню неправильно, тогда возникает другой вопрос: если число, назначенное конкретному адресу, больше, чем тип (как в myArray [2]), то не влияет ли это на значения, сохраненные в последующем адресе?
Пример: у нас есть int myNum = 4 миллиарда по адресу 0x10010000. Конечно, myNum не может хранить 4 миллиарда, поэтому он выглядит как отрицательное число по этому адресу. Несмотря на невозможность сохранить это большое число, оно не влияет на значение, сохраненное по последующему адресу 0x10010004. Правильный?
Адреса памяти просто имеют достаточно места для хранения чисел / символов определенного размера, и если размер превысит лимит, он будет представлен по-другому (например, попытка сохранить 4 миллиарда в int, но будет отображаться как отрицательное число) и так что это не влияет на числа / символы, хранящиеся по следующему адресу.
Извините, если я пошел за борт. У меня от этого весь день пукнул мозг.
источник
int c = INT.MAXINT; c+=1;
и посмотреть, что случилось с c.Ответы:
Нет. В C переменные имеют фиксированный набор адресов памяти для работы. Если вы работаете в системе с 4-байтовым кодом
ints
, и вы устанавливаетеint
переменную,2,147,483,647
а затем добавляете1
, переменная обычно будет содержать-2147483648
. (На большинстве систем. Поведение фактически не определено.) Другие области памяти не будут изменены.По сути, компилятор не позволит вам присвоить слишком большое значение для типа. Это сгенерирует ошибку компилятора. Если принудительно указать регистр, значение будет усечено.
Посмотрите побитовым образом, если тип может хранить только 8 битов, и вы пытаетесь ввести значение
1010101010101
в него с регистром, вы получите нижние 8 битов, или01010101
.В вашем примере, независимо от того, что вы делаете
myArray[2]
,myArray[3]
будет содержать «4». Там нет "разлива". Вы пытаетесь поместить что-то более чем в 4 байта, оно просто отбросит все на верхнем уровне, оставляя нижние 4 байта. На большинстве систем это приведет к-2147483648
.С практической точки зрения, вы хотите просто убедиться, что это никогда не произойдет. Эти виды переполнений часто приводят к трудно решаемым дефектам. Другими словами, если вы думаете, что есть хоть какой-то шанс, что ваши ценности будут исчисляться миллиардами, не используйте
int
.источник
Целочисленное переполнение со знаком - неопределенное поведение. Если это произойдет, ваша программа недействительна. Компилятор не обязан проверять это для вас, поэтому он может сгенерировать исполняемый файл, который, по-видимому, делает что-то разумное, но нет гарантии, что он это сделает.
Однако целочисленное переполнение без знака четко определено. Это обернет по модулю UINT_MAX + 1. Память, не занятая вашей переменной, не будет затронута.
Смотрите также https://stackoverflow.com/q/18195715/951890
источник
int
. я s'pose они могли бы использовать код Грея или BCD или EBCDIC . Не знаю, почему кто-то может проектировать аппаратные средства для выполнения арифметики с кодом Грея или EBCDIC, но опять же, я не знаю, почему кто-то будет делатьunsigned
с двоичным кодом и подписыватьint
что-либо, кроме дополнения 2.Итак, здесь есть две вещи:
На уровне языка:
В С:
Для тех, кто хотел бы "что-нибудь" пример, я видел:
превратиться в:
и да, это законное преобразование.
Это означает, что действительно существуют потенциальные риски перезаписи памяти при переполнении из-за какого-то странного преобразования компилятора.
Примечание: на Clang или gcc используйте
-fsanitize=undefined
в Debug для активации Undefined Behavior Sanitizer, который прервет работу при переполнении / переполнении целых чисел со знаком.Или это означает, что вы можете перезаписать память, используя результат операции для индексации (без проверки) в массив. К сожалению, это гораздо более вероятно при отсутствии обнаружения недостаточного / переполнения.
Примечание: на Clang или gcc используйте
-fsanitize=address
в Debug, чтобы активировать Address Sanitizer, который прервет работу за пределами доступа.На уровне машины :
Это действительно зависит от инструкций по сборке и используемого процессора:
Add
:Обратите внимание, что независимо от того, происходят ли события в регистрах или в памяти, ЦП ни в коем случае не перезаписывает память при переполнении.
источник
Для дальнейшего ответа @ StevenBurnap причина этого заключается в том, как компьютеры работают на уровне машины.
Ваш массив хранится в памяти (например, в оперативной памяти). Когда выполняется арифметическая операция, значение в памяти копируется во входные регистры схемы, которая выполняет арифметику (ALU: Арифметическая логическая единица ), затем выполняется операция с данными во входных регистрах, что дает результат в выходном регистре. Этот результат затем копируется обратно в память по правильному адресу в памяти, оставляя другие области памяти нетронутыми.
источник
Во-первых (предполагая стандарт C99), вы можете захотеть включить
<stdint.h>
стандартный заголовок и использовать некоторые из определенных здесь типов, в частности,int32_t
это ровно 32-разрядное целое число со знаком илиuint64_t
ровно 64-разрядное целое число без знака и т. Д. Возможно, вы захотите использовать такие типы, какint_fast16_t
по причинам производительности.Прочитайте ответы других, объяснив, что арифметика без знака никогда не проливается (или не переполняется) в соседние области памяти. Остерегайтесь неопределенного поведения при подписанном переполнении.
Затем, если вам нужно вычислить ровно огромные целые числа (например, вы хотите вычислить факториал 1000 со всеми 2568 цифрами в десятичном виде), вам нужно bigints или числа с произвольной точностью (или bignums). Алгоритмы для эффективной арифметики bigint очень умны и обычно требуют использования специализированных машинных инструкций (например, некоторые добавляют слово с переносом, если таковой имеется в вашем процессоре). Поэтому я настоятельно рекомендую в этом случае использовать некоторую существующую библиотеку bigint, такую как GMPlib
источник