2016-04-14 5 views
4

Я действительно хочу, чтобы понять, что происходит на этих двух линияхC++ - Выравнивание памяти

const int PAGES = 8 * 1024; 

// PAGES + extra 4KiB for alignment 
uint8_t * mem = new uint8_t [ PAGES * CCPU::PAGE_SIZE + CCPU::PAGE_SIZE ]; 

// align to a mutiple of 4KiB 
uint8_t * memAligned = (uint8_t *) ((((uintptr_t) mem) + CCPU::PAGE_SIZE - 1) & ~(uintptr_t) ~CCPU::ADDR_MASK); 

особенно в последней строке, я не понимаю, к чему ...

+0

TL; DR: он отнимает память и вам не нужен такой код :) –

+1

@KubaOber: Есть некоторые ограниченные обстоятельства, при которых выровненная страница памяти стоит отходов; некоторые ОС могут выполнять оптимизацию для уменьшения копий памяти, когда данные считываются/записываются в/из выровненной страницы. Если вам стоит одна страница памяти для получения нулевой копии передачи данных на веб-сервере с высокой нагрузкой, это того стоит. – ShadowRanger

+0

@ShadowRanger Я не говорю, что вы не должны выровнять свою память, просто потому, что вы должны сделать это, запросив явно выровненную строку. В конце концов, поскольку «СТРАНИЦЫ» являются «большими», распределитель, лежащий в основе «нового», попросит ОС для хранения. Таким образом, вы также можете сделать это сами и получить таким образом хранилище с выравниванием по страницам. Во многих или даже большинстве случаев «mem» будет уже выровнен, и тогда вы, возможно, увеличите «СТРАНИЦЫ», если это не будет const, и избегайте отходов, но я не думаю, что такие хаки следует поощрять. –

ответ

6

Это распределение указатель блок страницы с выравниванием по вертикали, то есть PAGES количество страниц с использованием диспетчеров C++ вместо специальных выделенных выделенных функций выделения (например, POSIX's posix_memalign or C11's aligned_alloc).

Сначала он выделяет PAGES + 1 страниц с памятью (это может быть или не выравниваться по странице), затем корректирует полученный указатель вперед так, чтобы он указывал на выровненный по первой строке байт в результате. Пополняя дополнительную страницу, он знает, что у нее, безусловно, будет достаточно большое выделение, чтобы за ней можно было использовать PAGES страниц. Программа просто должна быть уверена, что delete s mem, когда это будет сделано, а не memAligned (удаление последнего, вероятно, приведет к сбою программы сейчас, позже из-за повреждения кучи или просто утечки памяти, это неопределенное поведение, поэтому таяние вашего компьютера на шлак является юридическим поведением).

Эта последняя строка численно эквивалентна округлению до следующего кратного размера страницы; он добавляет PAGE_SIZE - 1 к указателю (поэтому, если указатель уже выровнен по странице, он все еще находится на той же странице, иначе он перемещается на следующую страницу), а затем маскирует низкие биты адреса (что отменяет добавление в «уже page aligned ", а во всех остальных случаях сбрасывает указатель на начало первой страницы, следующей за неузнанным указателем, в mem).

Детали: ~ является побитовое инвертирование, так ADDR_MASK, что, вероятно, что-то вроде 0x00000FFF для 4096 байт страниц, становится 0xFFFFF000 (листать все биты). Когда & -в значение, сохраняются только биты, установленные в обоих операндах. Чтобы привести примеры: для 32-битного указателя мы предположим, что new дал нам 0xDEADBEEF, а PAGE_SIZE - 4096. Добавление на 4095 (0xFFF) означает, что у нас есть «0xDEADCEEE». Затем мы маскируем 0xFFFFF000, что устраняет низкие разряды, давая нам 0xDEADC000, адрес, выровненный по первой странице, следующий за 0xDEADBEEF. То же самое произойдет с любым адресом, не привязанным к странице, который был возвращен new.

Если значение уже страница выровнены, хотя, скажем, 0xDEADB000, добавив на 4095/0xFFF получает нас 0xDEADBFFF (обратите внимание, как никаких битов в 0xDEADB не изменились), поэтому, когда мы маскировать, чтобы получить выровненный адрес, мы вернемся 0xDEADB000 снова , так как мы уже выровнены по странице.

Присвоение uintptr_t заключается в том, чтобы мы могли манипулировать адресом с помощью математических операторов и гарантировать, что побитовое инвертирование заполняет все биты, необходимые для соответствия указателю (если он не имеет размера должным образом, вы можете инвертировать, затем преобразовывать с повышением и вдруг у вас будет куча нулей слева, а не только справа, и вы в конечном итоге замаскируете важные биты в указателе, поэтому это указывает на совершенно другое и неправильное место).

+0

Спасибо, но я скорее подумал о '((uintptr_t) mem) + CCPU :: PAGE_SIZE - 1) & ~ (uintptr_t) ~ CCPU :: ADDR_MASK'. Что это за дикие '' 'и почему мы перепечатываем' uintptr_t'? – Charlestone

+0

@Charlestone: Я просто добавлял это объяснение. :-) Закончено сейчас, с примерами. – ShadowRanger

+0

О, я понимаю. uintptr_t в основном принимает адрес и отличает его как uint32_t (64_t)? еще одна вещь ~ ~ (uintptr_t) ~ CCPU :: ADDR_MASK' - почему здесь двойное отрицание? – Charlestone