Сколько памяти может быть адресовано 8-битным указателем, если минимальный размер объекта составляет 2 байта
Обновлено: 22.11.2024
Что мы подразумеваем под выравниванием данных, структурной упаковкой и заполнением?
Предсказать вывод следующей программы.
Прежде чем двигаться дальше, запишите свой ответ на бумаге и читайте дальше. Если вы настаиваете на объяснении, вы можете не понять какой-либо пробел в вашей аналогии. Выравнивание данных:
Каждый тип данных в C/C++ требует выравнивания (фактически это определяется архитектурой процессора, а не языком). Процессор будет иметь длину слова обработки, равную размеру шины данных. На 32-битной машине размер слова обработки будет 4 байта.
Исторически память имеет побайтовую адресацию и располагается последовательно. Если память организована как один банк шириной в один байт, процессору необходимо выполнить 4 цикла чтения памяти, чтобы получить целое число. Более экономично читать все 4 байта целого числа за один цикл памяти. Чтобы воспользоваться этим преимуществом, память будет организована в виде группы из 4 банков, как показано на рисунке выше.
Адресация памяти по-прежнему будет последовательной. Если банк 0 занимает адрес X, банк 1, банк 2 и банк 3 будут иметь адреса (X + 1), (X + 2) и (X + 3). Если по адресу X выделено целое число из 4 байтов (X кратно 4), процессору требуется только один цикл памяти для чтения всего целого числа.
Где как, если целое число выделено по адресу, отличному от кратного 4, он охватывает два ряда банков, как показано на рисунке ниже. Такое целое число требует двух циклов чтения памяти для выборки данных.
выравнивание данных переменной связано с тем, как данные хранятся в этих банках. Например, естественное выравнивание int на 32-битной машине составляет 4 байта. Когда тип данных выровнен естественным образом, ЦП извлекает его за минимальное количество циклов чтения.
Точно так же естественное выравнивание short int составляет 2 байта. Это означает, что short int может храниться в паре банк 0 — банк 1 или банк 2 — банк 3. double требует 8 байтов и занимает две строки в банках памяти. Любое несовпадение double приведет к более чем двум циклам чтения для выборки данных double.
Обратите внимание, что переменная double будет размещена на 8-байтовой границе на 32-битной машине. и требует двух циклов чтения памяти. На 64-битной машине, в зависимости от количества банков, двойная переменная будет размещена на границе 8 байт и требует только одного цикла чтения памяти.
Заполнение структуры:
В C/C++ в качестве данных используются структуры. пакет. Он не предоставляет каких-либо функций инкапсуляции или сокрытия данных (случай C++ является исключением из-за его семантического сходства с классами).
Из-за требований к выравниванию различных типов данных каждый член структуры должен быть выровнен естественным образом. Члены структуры располагаются в последовательно возрастающем порядке. Давайте проанализируем каждую структуру, объявленную в приведенной выше программе.
Вывод вышеуказанной программы:
Для удобства предположим, что каждая переменная типа структуры размещена на 4-байтовой границе (скажем, 0x0000), т.е. базовый адрес структуры кратно 4 (не обязательно всегда, см. объяснение structc_t).
структура A
Первый элемент structa_t — это char, выровненный по одному байту, за которым следует short int. короткий int выравнивается по 2 байта. Если элемент short int размещен сразу после элемента char, он начнется с нечетной границы адреса. Компилятор вставит байт заполнения после char, чтобы короткий int имел адрес, кратный 2 (т. е. выровненный по 2 байтам). Общий размер structa_t будет равен sizeof(char) + 1 (padding) + sizeof(short), 1 + 1 + 2 = 4 байта.
структура B
Первый элемент structb_t — это тип short int, за которым следует char. Поскольку char может находиться на любой границе байта, между short int и char не требуется заполнения, в общей сложности они занимают 3 байта. Следующий член — int. Если целое выделяется сразу, оно начнется с нечетной границы байта. Нам нужно 1 байт заполнения после члена char, чтобы адрес следующего члена int был выровнен по 4 байтам. Всего для structb_t требуется 2 + 1 + 1 (заполнение) + 4 = 8 байт.
структура C. Каждая структура также будет иметь требования к выравниванию.
Применяя тот же анализ, structc_t требует sizeof(char) + 7-байтовое заполнение + sizeof(double) + sizeof(int) = 1 + 7 + 8 + 4 = 20 байт. Однако sizeof(structc_t) будет 24 байта. Это связано с тем, что, наряду с элементами структуры, переменные типа структуры также будут иметь естественное выравнивание. Давайте разберемся на примере. Скажем, мы объявили массив structc_t, как показано ниже
Для упрощения расчетов предположим, что базовый адрес structc_array равен 0x0000. Если structc_t занимает 20 (0x14) байтов, как мы рассчитали, второй элемент массива structc_t (с индексом 1) будет иметь адрес 0x0000 + 0x0014 = 0x0014. Это начальный адрес элемента индекса 1 массива.Двойной член этой structc_t будет размещен по адресу 0x0014 + 0x1 + 0x7 = 0x001C (десятичное число 28), что не кратно 8 и противоречит требованиям выравнивания double. Как мы упоминали выше, требование выравнивания для double составляет 8 байт.
Чтобы избежать такого смещения, компилятор введет требование выравнивания для каждой структуры. Он будет как у самого крупного члена конструкции. В нашем случае выравнивание structa_t равно 2, structb_t равно 4 и structc_t равно 8. Если нам нужны вложенные структуры, размер наибольшей внутренней структуры будет выравниванием непосредственно большей структуры.
В structc_t приведенной выше программы будет добавлено 4 байта после элемента int, чтобы сделать размер структуры кратным ее выравниванию. Таким образом, sizeof (structc_t) составляет 24 байта. Это гарантирует правильное выравнивание даже в массивах. Вы можете перепроверить.
структура D – Как уменьшить отступы?
К настоящему моменту может быть ясно, что отступы неизбежны. Есть способ минимизировать отступы. Программист должен объявить элементы структуры в порядке возрастания/уменьшения их размера. Примером является structd_t, приведенный в нашем коде, размер которого составляет 16 байтов вместо 24 байтов structc_t.
Что такое упаковка структуры?
Иногда необходимо избегать заполнения байтов среди членов структуры. Например, чтение содержимого заголовка файла ELF или заголовка файла BMP или JPEG. Нам нужно определить структуру, аналогичную структуре заголовка, и отобразить ее. Однако следует проявлять осторожность при доступе к таким членам. Обычно чтение байт за байтом позволяет избежать исключений с неверным выравниванием. Это повлияет на производительность.
Большинство компиляторов предоставляют нестандартные расширения для отключения заполнения по умолчанию, такие как прагмы или переключатели командной строки. Обратитесь к документации соответствующего компилятора для получения более подробной информации.
Неполадки с указателями:
Возможна потенциальная ошибка при работе с арифметическими операциями с указателями. Например, разыменование универсального указателя (void *), как показано ниже, может вызвать исключение неправильного выравнивания,
Так что да, это немного унизительный вопрос, учитывая, что я изучаю компьютерные науки, но, тем не менее,
насколько велики адреса памяти? Я знаю, что это будет зависеть от машины, если это 64-битная или 32-битная машина и т. д. и т. д., но в С++ кажется, что каждый адрес имеет длину 8 бит или один байт, поэтому int в большинстве систем имеет длину 4 байта и указатель для int имеет длину всего один байт,
это говорит о том, что размер адреса памяти зависит от размера системы,8,16,32,64 и т. д.
поэтому я хочу уточнить, насколько велик адрес памяти
как в 32-битной, так и в 64-битной системе?
С++ кажется, что каждый адрес имеет длину 8 бит или один байт |
Практически в любой потребительской системе адрес памяти (то есть тип данных указателя) является 64-разрядным для 64-разрядного компьютера и 32-разрядным для 32-разрядного компьютера. Обратите внимание, что такие операционные системы, как Windows, по-прежнему могут запускать 32-битный код в 64-битной системе, поэтому в этих программах по-прежнему будут 32-битные адреса. [Обязательное замечание, что встроенный мир может быть другим, см. ваше руководство.]
Хороший исходный код не заботится о том, насколько велик указатель (адрес памяти).
Это всегда будет sizeof(
Размер указателя также зависит от типа данных. sizeof(int *) и sizeof(float *) не обязательно должны быть равны. Пустота * должна содержать любой заданный указатель данных (не обязательно указатели на функции), поэтому sizeof(void *) — это размер самых больших указателей данных на платформе в байтах.
Практически в любой потребительской системе адрес памяти (т. е. тип данных указателя) является 64-битным для 64-битного компьютера и 32-битным для 32-битного компьютера. . Обратите внимание, что такие операционные системы, как Windows, по-прежнему могут запускать 32-битный код в 64-битной системе, поэтому в этих программах по-прежнему будут 32-битные адреса. [Обязательно обратите внимание, что встроенный мир может быть другим, см. ваше руководство.] |
Значит, адрес памяти не 8-битный? почему char всего 8 бит, а int 4 байта, я был почти уверен, что размер адреса памяти равен 8 битам (256 возможных значений)
В большинстве систем адрес памяти, вероятно, не будет 8-битным. Определенно не что-то, работающее в современной среде рабочего стола.
почему char имеет размер всего 8 байт |
Чтобы вычислить количество битов, тип:
sizeof (type) * CHAR_BIT (CHAR_BIT определен в , и обычно равен 8)
кажется, что каждый адрес памяти имеет длину 8 бит или один байт
но int имеет длину 4 байта? как это повлияет на доступ к arr[256] и далее?
arr[512] представляет собой массив целых чисел, каждое из которых составляет 4 байта? так что arr[1] будет 4 байта + arr[0],arr[2] будет 8 байт плюс arr[0], как это повлияет на то, что мы превысим 256?
Две разные вещи. Размер адреса и размер самих данных.
Если у вас есть указатель типа T,
T* ptr;
а вы делаете
ptr++;
адрес увеличивается на sizeof(T).
Я думаю, вы неправильно читаете сообщение о переполнении стека. Одним из свойств компьютера является его «адресуемость», которая относится к наименьшей единице данных, которая может быть представлена адресом. Большинство современных компьютеров имеют побайтовую адресацию, что означает, что аппаратная инструкция может обращаться к одному байту данных и работать с ним.
Поэтому было бы правильно сказать, что *целью* адреса (на что указывает адрес) является байт в этом контексте. Но фактические данные, составляющие адрес, обычно намного больше на современных машинах, обычно 32 или 64 бита. Чем больше адрес (или то, что мы называем «указателем» в C и C++), тем больший диапазон он может представлять.
Надеюсь, это немного поможет.
Эта ссылка говорит об арифметике указателей. |
но допустим, у нас есть char,
char состоит всего из 8 бит или одного байта. так что было бы пустой тратой памяти хранить один символ в одном 32-битном адресе памяти, верно? значит ли это, что мы можем хранить несколько символов в одном 32-битном адресе??
Адреса памяти — это то, что хранится в указателях. Они относятся к расположению байта в памяти. Адрес переменной int — это место в памяти, где хранится первый байт переменной int.
char состоит только из 8 бит или одного байта. так что было бы пустой тратой памяти хранить один символ в одном 32-битном адресе памяти, верно? значит ли это, что мы можем хранить несколько символов в одном 32-битном адресе?? |
Мне нравится то, что сказал mzimmers, возможно, это прольет свет на это.
р>
Если у нас есть массив целых чисел и допустим, что sizeof(int) == 4, то это будет выглядеть так:
int arr[200];
&arr[0] == 1000
&arr[1] == 1004
&arr[2] == 1008
и т.д.
Сам по себе адрес не определяет размер объекта. Независимо от того, равен ли sizeof вашего объекта 4 или 4000, он все равно может иметь один и тот же адрес памяти.
Если у нас есть массив целых чисел и допустим, что sizeof(int) == 4, то это будет выглядеть так:
int arr[200];
&arr[0] == 1000
&arr[1] == 1004
&arr[2] == 1008
и т.д.
Должно быть, я что-то здесь упускаю, поэтому скажем, в первом примере, который вы привели, у нас есть массив символов размером в один байт, как вы сказали, адрес первого символа &arr[0] будет 1000 и [1] будет 1001, поэтому это означает, что адрес памяти 1000 равен 8 битам или одному байту, а не 32 битам (при условии, что мы работаем в 32-битной системе)
в примере с массивом int у нас есть массив целых чисел, каждое целое число равно 4 байтам (конечно, это может варьироваться, но по большей части это верно), поэтому это занимает адрес памяти от 1000 до 1004, первые 8 бит целого числа (конечно, в зависимости от того, является ли система большой или маленькой, но давайте проигнорируем это здесь) хранится в 1000, а следующие 8 бит хранятся в адресе 1002 и так далее, так что снова это означает, что адрес памяти равен 8 битам или байтам. ???
спасибо, что остаетесь со мной
Похоже, это проблема общения.
То, что вы называете адресом памяти, на самом деле называется позицией в памяти. Позиция памяти содержит значение, а адрес памяти используется для поиска позиции в памяти. По аналогии, если память — это отель, то позиция в памяти — это номер в отеле, а адрес — это номер комнаты. Если бы в вашем отеле был миллион одноместных номеров, номера ваших комнат имели бы размер 7 цифр, а номера имели бы размер «одна кровать».
Если бы у вас была память с побитовой адресацией (к отдельным битам можно получить доступ независимо) с 65 536 битами, ваши адреса были бы 16-битными, а позиции были бы однобитными.
Как сказал гелиос, возможно, нам следует вместо этого говорить позиция в памяти.
Не имеет значения, насколько велик сам тип данных int — он все равно может начинаться с позиции памяти 1000.
Чтобы получить доступ ко всем 1 миллиону деревьев, нам потребуется адрес памяти длиной не менее 20 бит, потому что 2 20 больше, чем один миллион.
Имеет ли смысл эта часть хотя бы сама по себе? Если нет, не читайте этот пост до конца.
Теперь, допустим, у нас есть миллион деревьев, а также имя игрока. Мы уже использовали первый миллион ячеек памяти для хранения данных нашего дерева. Итак, мы можем сохранить имя игрока, начиная с позиции 1 000 001.
Теперь, независимо от того, зовут меня «Билл» или «Навуходоносор», я все равно могу сказать, что имя игрока хранится, начиная с позиции 1 000 001. Но «Билл» будет занимать 4 единицы, а «Навуходоносор» — 14 единиц.
адрес 1 000 001 : B
адрес 1 000 002 : i
адрес 1 000 003 : l
адрес 1 000 004 : l
Возвращаясь к фактическим типам данных, допустим, я хочу сохранить 2-байтовый тип.
2-байтовое целое может выглядеть так: 1100 0011 1010 0111.
Мы собираемся сохранить это в ячейке памяти 2 миллиона, потому что мы уже используем младшие адреса для других объектов. Поскольку нам нужно адресовать не менее 2 миллионов позиций, нам нужно хранить этот адрес, используя не менее 21 бита (поскольку 2·20 > 2 миллиона) -- независимо от размера целого числа.
Теперь, поскольку sizeof(my 2-byte int) == 2, это означает, что 2 позиции в памяти будут "принадлежать" int.
Ячейка памяти 2 000 000 будет содержать "1100 0011".
Ячейка памяти 2 000 001 будет содержать «1010 0111».
Различные позиции памяти адресуются с шагом в байт, но это не означает, что сам адрес памяти является байтом (в нашем случае сам адрес памяти должен быть не менее 20). бит для хранения более 2 миллионов возможных адресов).
но почему нельзя хранить более 256 деревьев? Я понимаю, что 8 бит могут хранить только значения от 0 до 256, но что нам мешает создать массив больше 256? Я предполагаю, что для этого примера каждое дерево является однобитным, поэтому адрес памяти (если бы он был 8-битным) мог бы хранить 256 деревьев, так почему бы нам просто не иметь массив этих адресов памяти, скажем,деревья[500]< /p>
Итак, если я правильно понимаю, скажем, адрес памяти имеет длину 32 бита, что во многих случаях мы можем только 2 ^ 32 значения в нашем массиве? мы не можем превысить это число? Итак, скажем, нам нужен большой массив целых чисел arr[1000000000], который на 2 цифры длиннее предела, это было бы невозможно? так что у нас может быть не массив адресов памяти, а просто позиции памяти?
то, что вы вызываете адрес памяти, на самом деле называется позицией памяти. Позиция памяти содержит значение, а адрес памяти используется для поиска позиции в памяти. По аналогии, если память — это отель, то позиция в памяти — это номер в отеле, а адрес — это номер комнаты. Если бы в вашем отеле был миллион одноместных номеров, номера ваших комнат имели бы размер 7 цифр, а номера имели бы размер «одна кровать». Если бы у вас была память с побитовой адресацией (к отдельным битам можно получить доступ независимо) с 65 536 битами, ваши адреса были бы 16-битными, а позиции имели бы один бит. |
если я правильно понимаю, скажем, адрес памяти имеет длину 32 бита, что во многих случаях мы можем только 2 ^ 32 значения в нашем массиве? мы не можем превысить это число? |
Да, почти, при условии, что у вас столько памяти и что каждое значение занимает только 1 байт. На самом деле вы не сможете достичь предела, потому что часть памяти будет использоваться другими вещами.
скажем, нам нужен большой массив целых чисел arr[1000000000], который на 2 цифры длиннее ограничения, это невозможно? |
Вы правы, за исключением того, что 4000000000 на самом деле не превышает предела 2 32 (при условии, что sizeof(int) == 4).
Эта проблема является причиной того, что большинство новых компьютеров используют 64-разрядную версию, потому что мы хотим иметь возможность адресовать более 4 ГБ памяти.
Адреса памяти — это то, что хранится в указателях. Они относятся к расположению байта в памяти. Адрес переменной int — это место в памяти, где хранится первый байт переменной int. |
Возвращаясь к предыдущему,
Давайте, например, воспользуемся примером программы
хорошо, скажем, первое будет помещено в память с адресом 1000 и занимает 4 байта, поэтому этот int сначала получит свой собственный адрес памяти?
теперь второй будет хранить символ или байт, получит ли второй свой собственный адрес памяти или он будет делиться адресом памяти с первым и вместо этого будет указывать на позицию 1004, начиная с адреса памяти, начинающегося с 1000?
третьим будет один адрес памяти размером 4*200 байт? или он также будет совместно использовать адрес памяти одного, двух или обоих?
то же самое касается четвертого и пятого
если int имеет длину 4 байта, это означает, что байт 1000 не является фактическим адресом (ну, это всего лишь позиция в адресе), а позиция, с которой данные начинаются в этом адресе или мы могли бы использовать термин смещение? |
Я не совсем понимаю все, что вы здесь говорите. Похоже, вы неправильно понимаете, что такое адрес на самом деле.
Ваша программа имеет доступ к пространству памяти. Если это 32-битное приложение, это пространство памяти будет состоять из 2 32 адресов памяти. Не все адреса памяти будут сопоставляться с чем-либо, поэтому, если вы попытаетесь прочитать или записать такой адрес, вы, вероятно, получите ошибку сегментации, что приведет к сбою вашей программы.
Все в вашей программе использует одно и то же пространство памяти. Когда вы создаете объект, он будет расположен где-то в этом пространстве памяти. Адрес — это просто число, указывающее позицию, в которой объект хранится в этом пространстве памяти.
также, если int имеет длину 4 байта, это означает, что байт 1000 не является фактическим адресом |
но позиция начала данных |
Эта проблема является причиной того, что большинство новых компьютеров используют 64-битную систему, потому что мы хотим иметь возможность адресовать более 4 ГБ памяти. |