Как найти размер памяти любого массива

Обновлено: 07.07.2024

Общая форма объявления одномерного массива имеет следующий вид:

Как и другие переменные, массив должен быть объявлен явно, чтобы компилятор выделил для него определенную область памяти (т.е. разместил массив). Здесь тип обозначает базовый тип массива, являющийся типом каждого элемента. Размер задает количество элементов массива. Например, следующий оператор объявляет массив из 100 элементов типа double под именем balance :

Согласно стандарту С89 размер массива должен быть указан явно с помощью выражения-константы. Таким образом, в программе на С89 размер массива определяется во время компиляции и впоследствии остается неизменным. (В С99 определены массивы, размер которых определяется во время выполнения. О них еще будет идти речь далее в этой главе, а также более подробно в части II).

Доступ к элементу массива осуществляется с помощью имени массива и индекса. Индекс элемента массива помещается в квадратных скобках после имени. Например, оператор

присваивает 3-му элементу массива balance значение 12.23.

Индекс первого элемента любого массива в языке С равен нулю. Поэтому оператор

объявляет массив символов из 10 элементов — от р[0] до р[9] . В следующей программе вычисляются значения элементов массива целого типа с индексами от 0 до 99:

Объем памяти, необходимый для хранения массива, непосредственно определяется его типом и размером. Для одномерного массива количество байтов памяти вычисляется следующим образом:

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

Можно сказать, что одномерный массив — это список, хранящийся в непрерывной области памяти в порядке индексации. На рис. 4.1 показано, как хранится в памяти массив а , начинающийся по адресу 1000 и объявленный как

В языке С++ определены только одномерные массивы, но поскольку элементом массива может быть массив , возможно определить и двумерные массивы. Они определяются списком константных-выражений следующих за идентификатором массива, причем каждое константное- выражение заключается в свои квадратные скобки. Каждое константное- выражение в квадратных скобках определяет число элементов по данному измерению массива, так что объявление двумерного массива содержит два константных-выражения, трехмерного – три и т.д.

Объем занимаемой памяти в байтах для двухмерного массива вычисляется по формуле:

Если мы имеем дело с двумерным массивом B размерности MxN , расположенным в памяти по строкам, то адрес элемента B[i][j] вычисляется по формуле:

Так как массивы занимают непрерывный участок памяти, то двумерный массив размерности MxN можно рассматривать как одномерный массив из M указателей, которые являются константами. Константы -указатели содержат значения адресов M одномерных безымянных массивов. Поэтому обращение к элементу B[i][j] посредством B[i*N + j] невозможно, так как указателя с номером i*N + j может не существовать.

Пример 1. Определение размера памяти двумерного массива

Результат выполнения программы:

i_mas[10][10] занимает 400 байт – 4 байта (тип int ) * 10*10 (количество элементов массива)

f_mas[3][5]=,,> занимает 60 байт – 4 байта (тип float ) * 3*5 (объявленное количество элементов массива)

d_mas[2*q-r] [2*v/p] занимает 112 байт – 8 байт (тип double ) * 7*2 (вычисленное через формулу количество элементов массива)

r_mas[][3]=,,> занимает 36 байт – 4 байта (тип int ) * 3*3 (заданное количество элементов массива)

Указатели и двумерные массивы

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

где k – количество байтов, выделяемое для элемента массива (в зависимости от типа).

Указатели на двумерные массивы в языке С++ – это массивы массивов, т.е. такие массивы, элементами которых являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов. Например, при выполнении объявления двумерного массива:

  1. В памяти выделяется участок для хранения значения переменной arr , которая является указателем на массив из четырех указателей.
  2. Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех указателей содержит адрес одномерного массива из трех элементов типа int .
  3. Следовательно, в памяти компьютера выделяется четыре участка для хранения четырех массивов чисел типа int , каждый из которых состоит из трех элементов.

Схематично распределение памяти для данного двумерного массива выглядит так:

arr \\ \downarrow \\ arr[0]\ \to arr[0][0]\ arr[0][1]\ arr[0][2] \\ arr[1]\ \to arr[1][0]\ arr[1][1]\ arr[1][2] \\ arr[2]\ \to arr[2][0]\ arr[2][1]\ arr[2][2] \\ arr[3]\ \to arr[3][0]\ arr[3][1]\ arr[3][2]

Таким образом, объявление arr[4][3] порождает в программе три разных объекта:

  • указатель с идентификатором arr ,
  • безымянный массив из четырех указателей: arr[0], arr[1], arr[2], arr[3]
  • безымянный массив из двенадцати чисел типа int .
  1. Для доступа к безымянным массивам используются адресные выражения с указателем arr . Доступ к элементам одномерного массива указателей осуществляется с указанием одного индексного выражения в форме arr[2] или *(arr+2) .
  2. Для доступа к элементам двумерного массива чисел типа int arr[i][j] должны быть использованы следующие выражения:

Например, пусть i=1, j=2 , тогда обращение к элементу arr[1][2] :

  • arr[i][j] arr[1][2]=10
  • *(*(arr+i)+j) *(*(arr+1)+2)=10
  • (*(arr+i))[j] (*(arr+1))[2]=10

Причем внешне похожее обращение arr[5] выполнить невозможно, так как указателя с индексом 5 не существует.

Пример 2 . Использование индексных и адресных выражения при обработке двумерных массивов.

В этой статье я хочу исследовать расход памяти у массивов (и значений в целом) в PHP используя следующий скрипт в качестве примера, который создаёт 100 000 уникальных целочисленных элементов массива и в конце измеряет количество использованной памяти.

Это перевод (для таких как я, которые этого часто не замечают).

В начале я хочу поблагодарить Johannes и Tyrael за их помощь в поисках укромных мест расхода памяти.

Как вы думаете сколько получится? Если целое число это 8 байт (на 64 архитектурах и используя тип long) и есть 100 000 целых чисел, то, очевидно, потребуется 800 000 байт. Это около 0,76 Мб.

Теперь попробуйте запустить код. Это можно сделать on-line. В результате получится 14 649 024 байт. Да, вы не ослышались, это 13,97 Мб — в 18 раз больше, чем мы прикинули.

Итак, откуда появилось это 18 кратное увеличение?

Краткое изложение

Для тех, кто не хочет разбираться со всем этим, вот краткий обзор вовлечённых компонент.

Приведённые выше числа могут меняться в зависимости от вашей операционной системы, компилятора и опций компилирования. Например, если вы компилируете PHP с debug или thread-safety, то получите различные значения. Но я думаю, что приведённые размеры вы увидите на рядовой сборке PHP 5.3 на 64 разрядном Линуксе.

Если умножить эти 144 байта на наши 100 000 чисел, то получится 14 400 000 байт, что составляет 13,73 Мб. Довольно близко к реальному результату, остальное — это в основном указатели для неинициализированных блоков(buckets), но я расскажу об этом позже.

Теперь, если вы хотите иметь более детальный анализ значений, которые указаны выше, то читайте дальше :).

Объединение zvalue_value

Если вы не знаете C, то это не проблема — код очень прост: объединение означает, что значение может выступать в роли различных типов. Например, если вы используете zvalue_value->lval, то значение будет интерпретировано как целое число. С другой стороны, если используете zvalue_value->ht, то значение будет интерпретировано как указатель на хеш-таблицу (aka массив).

Не будем на этом задерживаться. Важным для нас только то, что размер объединения равен размеру его крупнейшего компонента. Самый большой компонент — это строка (на самом деле структура zend_object_value имеет тоже размер, но этот момент я опущу для простоты). Структура состоит из указателя (8 байт) и целого числа (4 байта). Итого 12 байт. Благодаря выравниванию памяти (структуры в 12 байт — это не круто, потому что они не являются произведением 64 бит/8 байт) конечный размер структуры будет 16 байт и, соответственно, всего объединения в целом.

Итак, теперь мы знаем, что нам нужно не 8 байт для каждого значения, а 16 — за счёт динамической типизации PHP. Умножив на 100 000 получим 1 600 000 байт, т.е. 1,53 Мб. Но реальный объём 13,97 Мб, поэтому мы не достигли пока цели.

Структура zval

Вполне логично, что union хранит только значение, а PHP, очевидно, нужно хранить так же его тип и некоторую информацию для сборки мусора. Структура, которая содержит эту информацию, называется zval и вы, наверное, уже слышали о ней. Для получения дополнительной информации о том, зачем это PHP, я рекомендую прочитать статью Sara Golemon. Как бы то ни было эта структура определяется следующим образом:

Размер структуры определяется суммой размеров всех её компонент: zvalue_value — 16 байт (расчёт выше), zend_uint — 4 байта, zend_uchar — 1 байт каждый. В общей сложности 22 байта. Опять же из-за выравнивания памяти реальный размер будет 24 байта.

Так что, если мы храним 100 000 значений по 24 байта, то это будет 2 400 000 байт или 2,29 Мб. Разрыв сокращается, но реальное значение ещё более чем в шесть раз больше.

Сборщик мусора для циклических ссылок (PHP 5.3)

PHP 5.3 представила новый сборщик мусора для циклических ссылок. Для этого PHP хранит некоторую дополнительную информацию. Я не хочу здесь объяснять как это работает, вы можете почерпнуть необходимую информацию из мануала. Для наших расчётов размеров важно, что каждый zval оборачивается zval_gc_info:

Как вы видите Zend только добавляет объединение, которое содержит два указателя. Как вы помните размер объединения определяется самым большим компонентом. Оба компонента — это указатели по 8 байт. Соответственно, размер объединения тоже 8 байт.

Если мы добавим полученные выше 24 байта, то мы получим 32 байта. Умножаем это на 100 000 и полуаем 3,05 Мб.

Менеджер памяти ZEND

Си, в отличие от PHP, не управляет памятью за вас. Вы должны самостоятельно следить за распределением памяти. Для этого PHP использует оптимизированный для своих нужд собственный менеджер памяти: The Zend Memory Manager. MM Zend основан на malloc от Doug Lea и всяческих дополнительных специфических для PHP особенностей и оптимизаций (таких как ограничение памяти, очистка после каждого запроса и тому подобное).

Что важного для нас в этом так это то, что MM добавляет заголовок для каждого выделения памяти, которое проходит через него. И определяется следующим образом:

Для примера мы будем считать, что все эти опции отключены. В этом случае остается только две компоненты size_t _size и _prev. size_t занимет 8 байт (64 бита), так что заголовок имеет размер в 16 байт — и этот заголовок добавляется для каждого выделения памяти.

Так что мы должны скорректировать размер zval снова. На самом деле это будет не 32 байта, а 48, из-за этого заголовка. Умножаем на наши 100 000 элементов и получаем 4,58 Мб. Реальный размер 13,97 Мб, так что мы уже покрыли примерно треть.

Блоки

Как вы видите необходимо хранить «груз» данных, чтобы получить абстрактный массив данных вроде такого, какой используется в PHP (массивы PHP являются массивами, словарями и связными списками в одно и тоже время, что, конечно, требует много данных). Размер отдельных компонент это: 8 байт для типа ulong, 4 байта для uint и 7 раз по 8 байт для указателей. В результате получается 68. Добавляем выравнивание и получаем 72 байта.

Для блоков как и для zval должны быть добавлены заголовки в 16 байт, что даёт нам 88 байт. Так же нам нужно хранить указатели на эти блоки в «настоящем» массиве C (Bucket **arBuckets;), я упомнил об этом выше, что добавляет ещё 8 байт на элемент. Так что в целом каждый блок расходует в 96 байтах памяти.

И так, если нам нужен блок для каждого значения — это будет 96 байт для bucket и 48 байт для zval, что составляет 144 байта в общей сложности. Для 100 000 элементов это будет 14 400 000 байт или 13,73 Мб.

Подождите, осталось ещё 0,24 Мб!

Эти последние 0,24 Мб обусловлены неинициализированными блоками: размер «реального» массива C в идеале должен быть равен количеству элементов. Таким образом мы получаем наименьшее количество коллизий (если вы не хотите тратить много памяти). Но PHP, очевидно, не может перераспределять весь массив каждый раз когда добавляется новый элемент — это было бы ооочень медленно. Вместо этого PHP всегда удваивает размер внутреннего массива блоков, если оно попадает в предел. Таким образом, размер массива всегда является степенью двойки.

В нашем случае это 2 ^ 17 = 131 072. Но нам нужно только 100 000 из этих блоков, поэтому мы оставляем 31 072 блока неиспользованными. Те, память под эти блоки выделена не будет (поэтому нам не надо тратить полные 96 байт), но память под указатель(который хранится в внутреннем массиве блоков) на блок должна быть использована. Поэтому мы дополнительно используем 8 байт (на указатель) * 31 072 элементов. Это 248 576 байт или 0,23 Мб. Что соответствует недостающей памяти. (Конечно, отсутствуют ещё несколько байт, но я не хочу полностью покрыть всё. Это такие вещи как сама структура хэш-таблицы, переменные и т.д.)

Загадка действительно решена.

О чём нам это говорит?

PHP не C. И это говорит нам только об этом. Вы не можете ожидать от супер-динамического языка PHP эффективного использования памяти как в C. Не можете и всё.

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

Посмотрим на модифицированный скрипт:

В основном он делает то же самое, но, если вы его запустите, то вы заметите, что он использует «всего лишь» 5 600 640 байт. Что составляет 56 байт на элемент, а это намного меньше, чем 144 байта на элемент обычного массива. Это происходит потому, что фиксированный массив не нуждается в bucket структуре: так что требуется только один zval (48 байт) и один указатель (8 байт) для каждого элемента, что даст нам наблюдаемые 56 байт.

То есть, количество элементов, которые может содержать массив?

ОТВЕТЫ

Ответ 1

Управляющее резюме:

Полный ответ:

Чтобы определить размер вашего массива в байтах, вы можете использовать оператор sizeof :

На моем компьютере длина целых 4 байта, поэтому n равно 68.

Чтобы определить количество элементов в массиве, мы можем разделить общий размер массива на размер элемента массива. Вы можете сделать это с типом, как это:

и получить правильный ответ (68/4 = 17), но если тип изменился вы бы неприятная ошибка, если вы забыли изменить a sizeof(int) , а также.

Поэтому предпочтительным делителем является sizeof(a[0]) , размер нулевого элемента массива.

Еще одним преимуществом является то, что теперь вы можете легко параметризовать имя массива в макросе и получить:

Ответ 2

Способ sizeof - это правильный путь iff, который вы имеете в виду массивы, не полученные в качестве параметров. Массив, отправленный как параметр функции, рассматривается как указатель, поэтому sizeof вернет размер указателя вместо массива.

Таким образом, внутри функций этот метод не работает. Вместо этого всегда передавайте дополнительный параметр size_t size , указывающий количество элементов в массиве.

Выход (в 64-разрядной ОС Linux):

Выход (в 32-разрядной ОС Windows):

Ответ 3

Стоит отметить, что sizeof не помогает при работе со значением массива, которое разложилось на указатель: хотя он указывает на начало массива, компилятору он совпадает с указателем на один элемент этого массива. Указатель не "помнит" ничего о массиве, который использовался для его инициализации.

Ответ 4

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

Как ясно из Википедии, C sizeof не является функцией; это оператор . Таким образом, он не требует скобок вокруг своего аргумента, если аргумент не является именем типа. Это легко запомнить, поскольку он делает аргумент похожим на литое выражение, которое также использует скобки.

Итак: Если у вас есть следующее:

Вы можете найти количество элементов с таким кодом:

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

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

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

Например, скажем, у вас есть функция, которая выводит некоторые данные в виде потока байтов, например, по сети. Позвольте вызвать функцию send() и заставить в качестве аргументов указывать указатель на отправляемый объект и количество байтов в объекте. Итак, прототип будет выглядеть следующим образом:

Теперь вы ввели тонкий способ стрелять себе в ногу, указав тип foo в двух местах. Если кто-то меняет, а другой - нет, код прерывается. Таким образом, всегда делайте это так:

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

Ответ 5

Откроется эта ссылка для объяснения

Ответ 6

Вы можете использовать оператор sizeof, но он не будет работать для функций, потому что для ссылки на указатель вы можете сделать следующее, чтобы найти длину массива:

Ответ 7

Если вам известен тип данных массива, вы можете использовать что-то вроде:

Или, если вы не знаете тип данных массива, вы можете использовать что-то вроде:

Примечание. Эта вещь работает только в том случае, если массив не определен во время выполнения (например, malloc), и массив не передается в функции. В обоих случаях arr (имя массива) является указателем.

Ответ 8

Макрос ARRAYELEMENTCOUNT(x) , который каждый использует, оценивает неправильно. Это реалистично, это просто деликатный вопрос, потому что вы не можете иметь выражения, которые приводят к типу "массив".

Фактически оценивается как:

Он правильно оценивает:

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

Это правильно; мой пример был плохим. Но это на самом деле то, что должно произойти. Как уже упоминалось ранее, p + 1 закончится как тип указателя и аннулирует весь макрос (как если бы вы попытались использовать макрос в функции с параметром указателя).

В конце дня, в данном конкретном случае, ошибка не имеет большого значения (поэтому я просто теряю время, huzzah!), потому что у вас нет выражений с типом "массива", Но на самом деле вопрос о тонкостях оценки препроцессора, я думаю, является важным.

Ответ 9

Для многомерных массивов это несколько сложнее. Часто люди определяют явные макроконстанты, т.е.

Но эти константы могут быть вычислены и во время компиляции с sizeof:

Обратите внимание, что этот код работает в C и С++. Для массивов с более чем двумя измерениями используйте

и т.д., ad infinitum.

Ответ 10

Размер массива в C:

Ответ 11

Ответ 12

"вы ввели тонкий способ стрелять себе в ногу"

C 'native' массивы не сохраняют свой размер. Поэтому рекомендуется сохранять длину массива в отдельной переменной /const и передавать его всякий раз, когда вы передаете массив, а именно:

Вы ДОЛЖНЫ всегда избегать встроенных массивов (если только вы не можете, и в этом случае, обратите внимание на вашу ногу). Если вы пишете С++, используйте контейнер STL. "По сравнению с массивами они обеспечивают почти ту же производительность", и они гораздо полезнее!

Ответ 13

Ответ 14

@Магнус: стандарт определяет sizeof как уступающий количеству байтов в объекте, а sizeof (char) всегда один. Количество бит в байте является специфичным для реализации.

Изменить: стандартный раздел ANSI С++. 5.3.3. Размер:

Оператор sizeof дает количество байтов в представлении объекта своего операнда. [. ] sizeof (char), sizeof (подпись char) и sizeof (без знака char) равны 1; результат sizeof, применяемый к любому другому фундаментальному типу, определяется реализацией.

Раздел 1.6 Модель памяти С++:

Основным блоком памяти в модели памяти С++ является байт. Байт, по меньшей мере, достаточно большой, чтобы содержать любой элемент базового набора символов выполнения и состоит из непрерывной последовательности бит, число которых определяется реализацией.

Ответ 15

@Skizz: Я уверен, что я прав, хотя лучший "источник", который я могу вам дать в данный момент, - это Википедия, из статьи о sizeof:

Википедия ошибается, Skizz прав. sizeof (char) равен 1, по определению.

Я имею в виду, просто внимательно прочитайте запись в Википедии, чтобы понять, что это неправильно. msgstr "кратные char". sizeof(char) никогда не может быть ничего, кроме "1". Если бы это было, скажем, 2, это означало бы, что sizeof(char) был в два раза меньше char!

Ответ 16

Если вы действительно хотите сделать это, чтобы передать свой массив, я предлагаю реализовать структуру для хранения указателя на тип, в котором вы хотите получить массив и целое число, представляющее размер массива. Затем вы можете передать это своим функциям. Просто присвойте значение переменной массива (указатель на первый элемент) этому указателю. Затем вы можете перейти Array.arr[i] , чтобы получить i-й элемент и использовать Array.size , чтобы получить количество элементов в массиве.

Я включил для вас какой-то код. Это не очень полезно, но вы можете расширить его с большим количеством функций. Если честно, если это то, что вы хотите, вы должны прекратить использовать C и использовать другой язык с этими встроенными функциями.

Ответ 17

Лучший способ сохранить эту информацию, например, в структуре:

Внедрите все необходимые функции, такие как создание, уничтожение, проверка равенства и все остальное, что вам нужно. Это легче передать в качестве параметра.

Ответ 18

Функция sizeof возвращает количество байтов, которое используется вашим массивом в памяти. Если вы хотите вычислить количество элементов в вашем массиве, вы должны разделить это число с типом переменной sizeof массива. Пусть say int array[10]; , если целочисленное целое число переменной на вашем компьютере равно 32 бит (или 4 байта), чтобы получить размер вашего массива, вы должны сделать следующее:

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