Вопрос

#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))

С помощью приведенного выше макроса, не мог бы кто-нибудь, пожалуйста, помочь мне разобраться в части "(s) -1", почему это?

а также макросы, такие как:

#define PAGE_ROUND_DOWN(x) (((ULONG_PTR)(x)) & (~(PAGE_SIZE-1)))
#define PAGE_ROUND_UP(x) ( (((ULONG_PTR)(x)) + PAGE_SIZE-1)  & (~(PAGE_SIZE-1)) ) 

Я знаю, что часть "(~(PAGE_SIZE-1)))" обнулит последние пять битов, но в остальном я ничего не понимаю, особенно в том, какую роль играет оператор '&'.

Спасибо,

Это было полезно?

Решение

В ROUND_UP макрос полагается на целочисленное деление для выполнения работы.Это будет работать только в том случае, если оба параметра являются целыми числами.Я предполагаю, что N должно ли число быть округлено и S это интервал, на который его следует округлить.Это, ROUND_UP(12, 5) должен возвращать 15, так как 15 - это первый интервал из 5, больший, чем 12.

Представьте, что мы округляли в меньшую сторону, а не в большую.В этом случае макрос был бы просто:

#define ROUND_DOWN(N,S) ((N / S) * S)

ROUND_DOWN(12,5) вернул бы 10, потому что (12/5) при целочисленном делении равно 2, а 2*5 равно 10.Но мы делаем не ROUND_DOWN, мы делаем ROUND_UP.Поэтому, прежде чем мы выполним целочисленное деление, мы хотим добавить столько, сколько сможем, без потери точности.Если бы мы добавили S, это сработало бы почти в каждом случае; ROUND_UP(11,5) стало бы (((11+5) / 5) * 5), и поскольку 16/5 при целочисленном делении равно 3, мы получили бы 15.

Проблема возникает, когда мы передаем число, которое уже округлено до указанного кратного. ROUND_UP(10, 5) вернул бы 15, и это неправильно.Итак, вместо добавления S, мы добавляем S-1.Это гарантирует, что мы никогда не будем перекладывать что-то в следующую "корзину" без необходимости.

В PAGE_ макросы имеют отношение к двоичной математике.Для простоты мы представим, что имеем дело с 8-битными значениями.Давайте предположим, что PAGE_SIZE является 0b00100000. PAGE_SIZE-1 является таким образом 0b00011111. ~(PAGE_SIZE-1) является ли тогда 0b11100000.

Двоичный файл & выстроит в линию два двоичных числа и оставит 1 в любом месте, где оба числа имели 1.Таким образом, если x был 0b01100111, операция проходила бы следующим образом:

  0b01100111  (x)
& 0b11100000  (~(PAGE_SIZE-1))
------------
  0b01100000

Вы заметите, что операция действительно обнулила только последние 5 бит.Вот и все.Но это была именно та операция, которая требовалась для округления до ближайшего интервала PAGE_SIZE.Обратите внимание, что это сработало только потому, что PAGE_SIZE была в точности равна степени 2.Это немного похоже на утверждение, что для любого произвольного десятичного числа вы можете округлить его до ближайшего 100, просто обнулив последние две цифры.Это работает отлично, и это действительно легко сделать, но не сработало бы вообще, если бы вы пытались округлить до ближайшего значения, кратного 76.

PAGE_ROUND_UP делает то же самое, но добавляет на страницу столько, сколько может, прежде чем отрезать ее.Это вроде как то, как я могу округлить до ближайшего кратного 100, добавив 99 к любому числу и тогда обнуление последних двух цифр.(Мы добавляем PAGE_SIZE-1 по той же причине, по которой мы добавили S-1 выше.)

Удачи вам с вашей виртуальной памятью!

Другие советы

Используя целочисленную арифметику, деление всегда округляется в меньшую сторону.Чтобы исправить это, вы добавляете максимально возможное число, которое не повлияет на результат, если исходное число было равномерно делимым.Для числа S это максимально возможное число равно S-1.

Округление в степень 2 является особенным, потому что вы можете сделать это с помощью битовых операций.Кратное 2 всегда будет иметь ноль в нижнем бите, кратное 4 всегда будет иметь ноль в двух нижних битах и т.д.Двоичное представление степени 2 представляет собой один бит , за которым следует набор нулей;вычитание 1 очистит этот бит и установит все биты вправо.Инвертирование этого значения создает битовую маску с нулями в тех местах, которые необходимо очистить.Оператор & очистит эти биты в вашем значении, тем самым округляя значение в меньшую сторону.Тот же трюк с добавлением (PAGE_SIZE-1) к исходному значению приводит к его округлению в большую сторону, а не в меньшую.

Макросы округления страницы предполагают, что `PAGE_SIZE - это степень двойки, например:

0x0400    -- 1 KiB
0x0800    -- 2 KiB`
0x1000    -- 4 KiB

Ценность PAGE_SIZE - 1, следовательно, все это один бит:

0x03FF
0x07FF
0x0FFF

Следовательно, если бы целые числа были 16 битами (вместо 32 или 64 - это экономит мне время на вводе текста), то значение ~(PAGE_SIZE-1) является:

0xFC00
0xFE00
0xF000

Когда вы принимаете значение x (предполагая, неправдоподобно для реальной жизни, но достаточно для целей изложения, что ULONG_PTR является 16-разрядным целым числом без знака) является 0xBFAB, тогда

PAGE_SIZE         PAGE_ROUND_DN(0xBFAB)   PAGE_ROUND_UP(0xBFAB)

0x0400     -->    0xBC00                  0xC000
0x0800     -->    0xB800                  0xC000
0x1000     -->    0xB000                  0xC000

Макросы округляют в меньшую и большую сторону до ближайшего значения, кратного размеру страницы.Последние пять битов будут обнулены только в том случае, если PAGE_SIZE == 0x20 (или 32).

Основываясь на текущем проекте стандарта (C99), этот макрос не совсем корректен, однако обратите внимание, что для отрицательных значений N результат почти наверняка будет неверным.

Формула:

#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))

Использует тот факт, что целочисленное деление округляет в меньшую сторону для неотрицательных целых чисел и использует S - 1 часть, чтобы заставить его вместо этого округлиться.

Однако при целочисленном делении округляется до нуля (C99, раздел 6.5.5.Мультипликативные операторы, пункт 6).Для отрицательных N, правильный способ "округления" - это:'N / S', ни больше, ни меньше.

Это становится еще более запутанным, если S также допускается отрицательное значение, но давайте даже не будем вдаваться в подробности...(см.: Как я могу гарантировать, что деление целых чисел всегда округляется в большую сторону? для более подробного обсуждения различных неправильных и одного или двух правильных решений)

& делает это таким..ну хорошо, давайте возьмем несколько двоичных чисел.

(with 1000 being page size)
PAGE_ROUND_UP(01101b)=
01101b+1000b-1b & ~(1000b-1b) =
01101b+111b & ~(111b) =
01101b+111b & ...11000b = (the ... means 1's continuing for size of ULONG)
10100b & 11000b=
10000b

Итак, как вы можете видеть (надеюсь) Это округляется путем добавления PAGE_SIZE к x, а затем и так, чтобы отменить нижние биты PAGE_SIZE, которые не установлены

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top