Сколько байтов в памяти занимает массив, если 4 байта выделены для целого числа

Обновлено: 07.07.2024

При объявлении переменной задается тип любого выражения, похожего на объявление. Таким образом, если у нас есть объявления int a, *b, c[], *(*d[])();, то в коде выражения a , *b, c[] и *(*d[])() будут оцениваться как целое число. Столкнувшись с объявлением, вам может быть трудно понять, что d — это массив указателей на функции, которые возвращают целочисленные указатели, но вы точно знаете, к какому типу он будет относиться при использовании в заданном контексте. Таким образом, вы знаете, что оператор a = *(*d[5])(x, y) поместит целое число в a, даже если вы не уверены, что произошло . Аналогичным образом можно сопоставить типы, убрав соответствующие уровни косвенных обращений: b = (*d[5])(x, y) будет хранить целочисленный указатель в b, а не значение целого числа.

Хотя выражение, данное в объявлении, обычно является правильным способом использования переменной, связь между указателями и массивами допускает другие применения. Поскольку имя массива — это всего лишь указатель, мы можем также использовать выражения b[] и *c. (Использование альтернативной записи часто сбивает с толку, но иногда более ясно.) Например, шестой элемент массива, объявленный любым из двух упомянутых выше методов, может быть доступен любым из двух следующих методов: b[ 5] или *(b+5) (напомним, что одноместный оператор "*" просто берет значение справа и выполняет один уровень косвенности на it.) Второй метод добавляет 5*(размер типа элемента массива) к адресу массива, в результате чего получается указатель на шестой элемент, а затем «*» вызывает косвенное обращение к этому адресу, в результате чего сохраняется значение там. Обозначение с индексом является просто сокращением для этого.

Для лаборатории ваши программы тестирования могут знать, что ввод ограничен 1000 цифрами, но ваш множитель не должен знать об этом. Таким образом, вам нужно будет размещать внутри этих функций одномерные массивы непредсказуемого размера. Для этого используйте функцию malloc системной библиотеки, которая даст вам указанное количество непрерывных байтов памяти. Сначала вы должны в своих объявлениях сообщить компилятору тип возвращаемого значения malloc с помощью объявления (вместе с вашими объявлениями переменных): char *malloc();

Теперь предположим, что вам нужен массив из 10 целых чисел. Пусть A — целочисленный указатель (объявленный int *A). Чтобы получить массив, используйте команду: A = (int *) malloc( 10 * sizeof(int) ); Функция sizeof() расширяется компилятором до быть количеством байтов в одном элементе типа, заданного в качестве аргумента. Таким образом, если целое число содержит 4 байта, malloc вернет 40 байт (начиная с границы двойного слова, чтобы гарантировать правильное выравнивание многобайтовых объектов — вам не нужно об этом беспокоиться).

(int *), предшествующий вызову функции, называется приведением. Это изменяет тип возвращаемого значения malloc (который является символьным указателем) на целочисленный указатель, чтобы его можно было сохранить в A. Часто приведения влияют только на внутреннее представление компилятором того, что такое тип, но иногда они генерируют код для физического изменения данных, поэтому вы почти всегда должны использовать их при смешивании объектов разных типов.

Всегда проверяйте после вызова malloc, что вы действительно получили запрошенное пространство. Malloc вернет нулевой указатель, если не сможет получить место. Нулевой указатель может быть представлен в вашем коде как ноль или как NULL, если вы включаете стандартный файл ввода-вывода.

Наконец, функция free используется для возврата операционной системе пространства, выделенного с помощью malloc. Вы должны указать в качестве параметра, чтобы освободить точно указатель, предоставленный вам malloc . Вы не можете вернуть часть распределения. Таким образом, вызовов free должно быть столько же, сколько вызовов malloc, и они должны задействовать одни и те же блоки памяти.

Многомерные массивы

C использует две реализации массивов, в зависимости от объявления. Они одинаковы для одного измерения, но отличаются для других.

Например, если массив объявлен как int array[10][20][30];, то выделено ровно 6000 целых чисел и ссылка вида array[i][j][k] будет преобразован в *( array + i*20*30 + j*30 + k ), который вычисляет правильное смещение от указателя " массив", а затем делает косвенное обращение к нему. Чтобы передать массив этого типа в процедуру, вы должны объявить параметр как proc( arg ) int arg[][20][30]; (Вы можете объявить значение первого измерения, но компилятору все равно, так как он не нужен.)

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

Второй тип массива — это структура указателя-вектора, где каждое измерение представлено вектором указателей объектов следующего измерения, кроме последнего измерения, состоящего из массивов данных. Звучит как беспорядок, но на самом деле это не так:

Если трехмерный массив объявлен как int ***array; (и мы будем считать, что на данный момент было выделено место для массива 10*20*30), то имеется массив из 10 указателей на целые числа, 10 массивов из 20 указателей на целые числа и 6000 целых чисел. Каждый из 200 элементов из 10 массивов указывает на блок из 30 целых чисел, а каждый из 10 элементов одного массива указывает на один из 10 массивов. Переменная массива указывает на начало массива из 10 элементов.

Короче говоря, "array" указывает на указатель на указатель на целое число, "*array" указывает на указатель на целое число, " **массив" указывает на целое число, а "***массив" является целым числом.

В этом случае доступ в форме array[i][j][k] приводит к доступу в форме *( *( *(array+i) + к) + к)

Что означает: взять указатель на основной массив, добавить i для смещения к указателю на правильный массив второго измерения и косвенно к нему. Теперь у нас есть указатель на один из массивов из 20 указателей, и мы добавляем j, чтобы получить смещение к следующему измерению, и делаем для этого косвенное указание. Теперь у нас есть указатель на массив из 30 целых чисел, поэтому мы добавляем k, чтобы получить указатель на нужное целое число, делаем косвенное обращение и получаем целое число.

Передача массивов этого типа проста, вы объявляете параметр так же, как "int ***arrayname".

Теперь начинается самое интересное: как выделить память для массива указателей и векторов. Мы получаем память с помощью функции char *malloc( nbytes ); malloc возвращает символьный указатель на непрерывный блок из n байтов или указатель NULL (NULL< /tt> определен в пакете библиотеки), если он не может получить место.

Как и прежде, мы предполагаем, что переменная определена как int ***array;, и мы хотим, чтобы размеры были 10*20*30 (все нижеперечисленное можно сделать для произвольное i,j,k, что ближе к тому, что вам нужно).

Во-первых, нам нужен массив из 10 int **, поэтому мы используем следующее: array = (int ***) malloc( 10 * sizeof(int ** ) ); Функция sizeof возвращает целое число, указывающее, сколько байтов требуется для чего-то типа "int **", и нам нужно 10 из них. "(int ***)" — это преобразование, которое изменяет тип указателя с "char *" на "int ***" , чтобы типы были правильными. Не забывайте, что после этого вызова malloc вы должны проверить, равен ли array==NULL.

Примечание: malloc запрашивает 10 int **, но возвращает указатель на них, поэтому результатом является int ***.

Теперь, когда у нас есть 10 указателей, мы можем получить следующий уровень указателей: И, наконец, мы можем заполнить каждый из этих указателей массивом из 30 целых чисел: Опять же, помните, что каждый вызов malloc должен проверять результат . Также обратите внимание, что мы могли бы объединить два описанных выше шага, заполняя каждый набор из 20 указателей по мере их получения.

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

Это метод, который вы должны использовать, но вам, вероятно, понадобятся только двумерные массивы, которые намного проще, как только вы поймете этот пример.

Пробел возвращается в систему командой free( pointer );

Если я создам 10 целых чисел и целочисленный массив из 10, будет ли разница в общем занимаемом пространстве?

Мне нужно создать логический массив из миллионов записей, поэтому я хочу понять, сколько места займет сам массив.

я хочу знать, если я создам логический массив из миллионов записей, сколько места займет массив

9 ответов 9

Массив целых чисел представлен в виде блока памяти для хранения целых чисел и заголовка объекта. Заголовок объекта обычно занимает 3 32-битных слова для 32-битной JVM, но это зависит от платформы. (Заголовок содержит несколько битов флага, ссылку на дескриптор класса, пространство для информации о примитивной блокировке и длину фактического массива. Плюс заполнение.)

Таким образом, массив из 10 целых чисел, вероятно, занимает область 13 * 4 байта.

В случае с Integer[] каждый объект Integer имеет заголовок из 2 слов и поле из 1 слова, содержащее фактическое значение. И вам также нужно добавить заполнение и 1 слово (или 1-2 слова на 64-битной JVM) для ссылки. Обычно это 5 слов или 20 байтов на элемент массива. если только некоторые объекты Integer не появляются в нескольких местах массива.

  1. Количество слов, фактически используемых для ссылки на 64-битной JVM, зависит от того, используются ли "сжатые операторы-операторы".
  2. На некоторых JVM узлы кучи выделяются кратно 16 байтам. что увеличивает использование пространства (например, упомянутое выше заполнение).
  3. Если вы берете хэш-код идентификатора объекта и он выдерживает следующую сборку мусора, его размер увеличивается как минимум на 4 байта для кэширования значения хэш-кода.
  4. Все эти числа зависят от версии и поставщика, в дополнение к перечисленным выше источникам изменчивости.

@Thilo На самом деле предсказать размер указателя непросто. Из-за выравнивания JVM все еще может обойтись 4-байтовыми указателями. Это то, что я наблюдаю в своей системе.

Некоторые грубые расчеты нижних границ:

Каждый int занимает четыре байта. = 40 байт для десяти

Массив int занимает четыре байта для каждого компонента плюс четыре байта для хранения длины плюс еще четыре байта для хранения ссылки на него. = 48 байт (+, возможно, некоторое дополнение для выравнивания всех объектов по 8-байтовым границам)

Целое число занимает не менее 8 байт плюс еще четыре байта для хранения ссылки на него. = не менее 120 для десяти

Массив целых чисел занимает не менее 120 байтов для десяти целых чисел плюс четыре байта для длины, а затем, возможно, некоторое дополнение для выравнивания. Плюс четыре байта для хранения ссылки на него. (@Marko сообщает, что он даже измерил около 28 байтов на слот, так что это будет 280 байтов для массива из десяти).

В 64-разрядной версии Java все указатели занимают восемь байтов вместо четырех, что делает разницу между примитивом и оболочкой еще больше.

Я измерил, что Integer[], полный экземпляров Integer, занимает в среднем 28 байт на слот (64-разрядная JVM). Забавно, что это точно так же, как массив Object s.

В java у вас есть как Integer, так и int. Предположим, вы имеете в виду int , массив целых чисел считается объектом, а объекты имеют метаданные, поэтому массив из 10 целых чисел будет занимать более 10 переменных типа int

В свете вашего комментария не будет большой разницы, если вы используете массив. Массив будет использовать незначительный объем памяти для своей функциональности. Вся остальная память будет использоваться сохраненными объектами.

EDIT: вам нужно понять разницу между булевой оболочкой и логическим примитивным типом. Типы-оболочки обычно занимают больше места, чем примитивы. Так что для рекордных миссий старайтесь использовать примитивы.

Еще одна вещь, о которой следует помнить при работе с отчетными миссиями, как вы сказали, это Java Autoboxing. Снижение производительности может быть значительным, если вы непреднамеренно используете это в функции, которая просматривает весь массив.

Однако разница может быть довольно большой, если это Integer[] против int[] . В этом случае объекты-оболочки будут занимать большую часть места.

Согласен. Ответ отредактирован. Но я думаю, что семантика массива займет ту же память. Основное отличие заключается в объектах/элементах, хранящихся в массиве. Так что я не думаю, что я был не в тему.

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

Конечно, это соответствует стандартному отказу от ответственности: gc() не дает особых гарантий, поэтому повторите несколько раз, чтобы увидеть, получаете ли вы согласованные результаты. На моей машине ответ — один байт на логическое значение.

@Thilo Если бы у вас уже была работающая система, это был бы ваш лучший вариант, но в этом случае это просто дополнительная работа по копанию дампа.

@Thilo Для полноты картины я только что проверил подход к дампу. Он сообщил о занятости 64 МБ при добавлении указанных размеров выделения, но при этом общая куча занимает 56 МБ. Отличие в том, что в массиве объектов указано 8 байт/слот, хотя на самом деле это 4 байта/слот. Я определенно придерживаюсь своего подхода :-)

Это не должно плохо отражаться на преподавателе/интервьюере.

То, насколько вы заботитесь о размере и выравнивании переменных в памяти, зависит от того, насколько производительным должен быть ваш код. Очень важно, обрабатывает ли ваше программное обеспечение транзакции (например, EFT / фондовый рынок).

Размер, выравнивание и упаковка ваших переменных в памяти могут повлиять на попадание/промах в кэш ЦП, что может повлиять на производительность вашего кода до 100 раз.

Неплохо знать, что происходит на низком уровне, если вы ответственно используете приемы повышения производительности.

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

Массив – это индексированная коллекция элементов данных одного типа.
1) Индексация означает, что элементы массива пронумерованы (начиная с 0).
2) Ограничение одного типа важно, т.к. массивы хранятся в последовательных ячейках памяти. Все ячейки должны быть одного типа (и, следовательно, одного размера).

Объявление массивов:

Объявление массива похоже на форму обычного объявления (typeName variableName), но мы добавляем размер:

Это объявляет массив указанного размера с именем имя_переменной и типом имя_типа. Массив индексируется от 0 до size-1. Размер (в скобках) должен быть целочисленным литералом или постоянной переменной. Компилятор использует размер, чтобы определить, сколько места нужно выделить (т. е. сколько байтов).

Последний пример иллюстрирует двумерный массив (который нам часто нравится представлять как таблицу). Обычно мы думаем о первом размере как о строках, а о втором — как о столбцах, но на самом деле это не имеет значения, если вы последовательны! Таким образом, мы могли бы думать о последнем объявлении как о таблице с 5 строками и 10 столбцами, например.

Инициализация массивов:

Или мы могли бы просто инициализировать переменную в самом операторе объявления:

Можем ли мы сделать то же самое для массивов? Да, для встроенных типов. Просто перечислите значения массива (литералы) в обозначении набора < > после объявления. Вот несколько примеров:

Строки в стиле C

  • Мы часто используем строки, но в языке нет встроенного строкового типа
  • Строка в стиле C реализована в виде массива типа char, который заканчивается специальным символом, называемым "нулевым символом".
    • Нулевой символ имеет значение ASCII 0
    • Нулевой символ можно записать как литерал в коде следующим образом: '\0'

    Поскольку массивы символов используются для хранения строк в стиле C, вы можете инициализировать массив символов строковым литералом (то есть строкой в ​​двойных кавычках), если в выделенном пространстве остается место для нулевого символа.< /p>

    Обратите внимание, что это будет эквивалентно:

    Варианты инициализации

    Объявления массива должны содержать информацию о размере массива. Можно оставить размер вне [ ] в объявлении, если вы инициализируете массив встроенно, и в этом случае массив делается достаточно большим, чтобы захватить инициализированные данные. Примеры:

    Еще один быстрый способ использования наборов инициализаторов – использование меньшего количества элементов, чем указано в размере. Остальные элементы по умолчанию будут равны 0. Использование набора, содержащего больше элементов, чем выделенный размер, является незаконным.

    Примечание. Использование инициализаторов в объявлении, как в приведенных выше примерах, вероятно, не будет таким желательным для очень больших массивов.
    Еще один распространенный способ инициализации массива — с помощью цикла for:
    В этом примере массив numList инициализируется значением .

    Использование массивов:

    После объявления ваших массивов вы получаете доступ к элементам в массиве с именем массива и порядковым номером в квадратных скобках [ ]. Если массив объявлен как: typeName varName[size], то элемент с индексом n упоминается как varName[n]. Примеры:

    Однако нецелесообразно использовать индекс массива, выходящий за пределы допустимых индексов массива:

    Однако приведенное выше утверждение синтаксически допустимо. Работа программиста состоит в том, чтобы убедиться, что индексы за пределами границ не используются. Не рассчитывайте на то, что компилятор проверит это за вас — этого не произойдет!

    Копирование массивов:

    С переменными мы используем оператор присваивания, так что это было бы естественной тенденцией, но это неправильно!

    Мы должны копировать между массивами поэлементно. Однако цикл for упрощает эту задачу:

    Простой ввод-вывод со строками:

    В особом случае строк (массивы символов с нулем в конце) их можно использовать как обычные массивы. Доступ к одному элементу массива означает доступ к одному символу.

    Строки также могут выводиться и вводиться целиком со стандартными объектами ввода и вывода (cin и cout):
    Следующая строка выводит слово "Hello": Будьте осторожны, используйте это только для массивов символов которые используются как строки в стиле C. (Это означает, что только если нулевой символ присутствует в качестве терминатора).

    Следующая строка позволяет ввести слово (до 19 символов и завершающий нулевой символ) с клавиатуры, которое хранится в массиве word1:

    Символы считываются с клавиатуры до тех пор, пока не встретится первый "пробел" (пробел, табуляция, новая строка и т. д.). Ввод сохраняется в массиве символов, и нулевой символ добавляется автоматически.

    Читайте также: