Программно определить размер массива C++?

Этот вопрос был вызван аналогичным вопросом: Как delete [] «знает» размер массива операндов?

Мой вопрос немного отличается: Есть ли способ программно определить размер массива C++? А если нет, то почему? Каждая функция, которую я видел, которая принимает массив, также требует целочисленного параметра для задания размера. Но, как указано в связанном вопросе, delete[] должен знать размер памяти, которую нужно освободить.

Рассмотрим этот код C++:

int* arr = new int[256];
printf("Size of arr: %d\n", sizeof(arr));

Это напечатает «Size of arr: 4», который равен размеру указателя. Было бы неплохо иметь функцию, которая печатает 256, но я не думаю, что она существует в C++. (Опять же, отчасти вопрос в том, почему его не существует.)

Разъяснение: Я знаю, что если бы я объявил массив в стеке вместо кучи (т.е. «int arr[256];»), то оператор sizeof вернет 1024 (длина массива * sizeof (int)).

Фактически, если вы разместили массив в стеке, оператор sizeof вернет 1024, что составляет 256 (количество элементов) * 4 (размер отдельного элемента). (sizeof (arr) / sizeof (arr [0])) даст результат 256.

Kevin 13.10.2008 20:03

спасибо, я пропустил это, потому что на самом деле я использовал char [] в своем тестовом коде (и sizeof (char) == 1)

Kip 13.10.2008 20:20

Хотя это только гипотетически - поскольку это не работает - я должен указать, что вам следовало написать printf("Size of arr: %d\n", sizeof(*arr)); вместо printf("Size of arr: %d\n", sizeof(*arr));, поскольку вы хотите получить размер разыменованного указателя.

mg30rg 12.06.2014 14:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
62
3
64 880
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

Обычный способ справиться с этим - использовать вектор

int main()
{
   std::vector<int> v(256);
   printf("size of v is %i capacity is %i\n", sizeof(int) * v.size(), sizeof(int) * v.capacity());
}

или заранее определите размер

const int arrSize = 256;
int main()
{
    int array[arrSize];
    printf("Size of array is %i", sizeof(int) * arrSize);
}

sizeof(int) * arrSize такой же, как malloc ('sizeof (int) * arrSize'), не так ли ??

kAmol 10.09.2014 09:40

Это потому, что ваша переменная arr - всего лишь указатель. Он хранит адрес определенного места в памяти, ничего не зная об этом. Вы объявляете его как int *, что дает компилятору некоторое представление о том, что делать, когда вы увеличиваете указатель. Кроме того, вы могли указывать на начало или конец массива, либо в стек, либо в недопустимую память. Но я согласен с вами, отсутствие возможности вызвать sizeof очень раздражает :)

QuantumPete

Но система каким-то образом знает размер массива, иначе "delete [] arr" не сработает.

Kip 13.10.2008 19:01

Что ж, система знает во время выполнения, но sizeof - это вызов времени компиляции.

Peter Kühne 13.10.2008 19:11

delete [] arr будет знать размер массива, но не будет ли массив размещен в стеке.

Alexander 13.10.2008 19:31

Нет, нет никакого способа сделать это, вы должны следить за тем, насколько он велик внешне. Такие классы, как std::vector, делают это за вас.

Нет, в Standard C++ это невозможно.

Нет действительно веской причины, о которой я знаю. Вероятно, размер считался деталью реализации и лучше не раскрывать. Обратите внимание: когда вы говорите malloc (1000), нет гарантии, что возвращаемый блок составляет 1000 байт - только то, что это по меньшей мере 1000 байт. Скорее всего, это около 1020 (1 КБ минус 4 байта на накладные расходы). В этом случае размер «1020» является важным для запоминания библиотеки времени выполнения. И, конечно, это будет меняться между реализациями.

Вот почему комитет по стандартам добавил std: vector <>, который отслеживает его точный размер.

Следует отметить, что new [] также сохраняет количество запрошенных элементов, чтобы вызвать правильное количество конструкторов и деструкторов для массива. Где это хранится, снова зависит от реализации. Причина, по которой я не могу указать способ получения, находится вне моего понимания.

workmad3 13.10.2008 19:05

Я думаю, что "веская причина" в том, что массивы вообще не являются объектами. Массив - это просто необработанный блок памяти. Размер - это данные управления памятью, а не объектные данные. Вы могли бы написать класс Array, который отслеживал бы память и размер, но вы могли бы просто использовать std :: vector и не беспокоиться об этом.

Herms 13.10.2008 19:09

Ага ... Конечно. Тип int * не может знать, был ли массив, на который он указывал, новым массивом, локальным массивом или каким-то местом в середине массива.

James Curran 13.10.2008 19:13

@Herms: std :: string [10] определенно не является необработанной памятью, но это массив.

MSalters 14.10.2008 11:31

workmad3, возможно, только для элементов с нетривиальным деструктором и для типов с определяемым пользователем оператором delete, который хочет, чтобы размер был известен. для чего-либо другого достаточно не хранить номер

Johannes Schaub - litb 07.01.2009 14:49

Было бы очень удобно иметь функцию, которая возвращала бы размер выделения особенно, если бы он был больше запрошенного.

Mooing Duck 30.04.2012 10:19
Ответ принят как подходящий

delete [] знает размер, который был выделен. Однако эти знания находятся во время выполнения или в диспетчере памяти операционной системы, что означает, что они недоступны для компилятора во время компиляции. И sizeof() не является реальной функцией, он фактически вычисляется компилятором как константа, чего он не может сделать для динамически выделяемых массивов, размер которых неизвестен во время компиляции.

Также рассмотрите этот пример:


int *arr = new int[256];
int *p = &arr[100];
printf("Size: %d\n", sizeof(p));

Как компилятор узнает размер p? Корень проблемы в том, что массивы в C и C++ не являются объектами первого класса. Они распадаются на указатели, и компилятор или сама программа не могут узнать, указывает ли указатель на начало блока памяти, выделенного new, или на отдельный объект, или на какое-то место в середине кусок памяти, выделенный new.

Одна из причин этого заключается в том, что C и C++ оставляют управление памятью на усмотрение программиста и операционной системы, поэтому в них также отсутствует сборка мусора. Реализация new и delete не является частью стандарта C++, поскольку C++ предназначен для использования на различных платформах, которые могут управлять своей памятью по-разному. Можно позволить C++ отслеживать все выделенные массивы и их размеры, если вы пишете текстовый процессор для окна Windows, работающего на новейшем процессоре Intel, но это может быть совершенно невозможно, когда вы пишете встроенную систему, работающую на DSP.

В C++ есть абсолютно массивы. А как еще вы объяснили бы, почему с этим "char x [4]; size_t sz = sizeof (x);" что 'sz' будет присвоено 4?

Kevin 13.10.2008 20:06

Кевин, x на самом деле не массив. Это указатель на кусок памяти, который находится в стеке. А поскольку он был размещен в стеке статически, компилятор знает его размер. Если вы передадите x функции, она не узнает своего размера. Массивы в C или C++ не являются объектами первого класса.

Dima 13.10.2008 20:25

В качестве примечания: sizeof - это оператор, а не функция, как указано на плакате, что это не настоящая функция.

xk0der 07.01.2009 14:33

Дима, есть совсем массивы. массивы отличаются от указателей. к сожалению, многие учителя путают это и говорят своим ученикам, что они «всего лишь» указатели. нет, это не так. как еще объяснить это: char const ** s = & "bar"; не компилируется? [...]

Johannes Schaub - litb 07.01.2009 14:41

(если вы утверждаете, что «bar» - это указатель на символьный массив «bar», который является «просто» указателем?). и тот факт, что массивы не являются объектами первого класса, не означает, что «настоящих массивов нет». функции тоже не первоклассные. так разве у вас нет «настоящих функций» в C++?

Johannes Schaub - litb 07.01.2009 14:48

litb, причина char const ** s = & "bar"; не компилируется, так как "bar" является константой, а не lvalue, поэтому вы не можете взять его адрес. Это то же самое, что int * p = & 5; который тоже не компилируется.

Dima 07.01.2009 21:27

Говоря об отсутствии «настоящих» массивов, я имел в виду именно то, что массивы C или C++ не являются объектами первого класса. Вы, конечно, используете массивы в C или C++, но функция void foo (int * a), принимающая указатель в качестве аргумента, не знает, является ли a массивом или указателем на одно целое число.

Dima 07.01.2009 21:30

@Dima, тогда отредактируйте ответ, чтобы это было сказано. Я могу написать функции, которые принимают массивы, но не указатели, если вам нужны дополнительные доказательства.

Mooing Duck 30.04.2012 10:15

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

Mooing Duck 30.04.2012 10:16

@MooingDuck, я считаю, что мой ответ уже достаточно ясен.

Dima 01.05.2012 17:07

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

Mooing Duck 01.05.2012 18:58

void foo(int *a); принимает указатель, void foo(int (&a)[5]); принимает массив. Имена массивов превращаются в указатели, что отстой, но это не значит, что массивы и указатели - одно и то же.

Cat Plus Plus 01.05.2012 20:16

@ Дима: Ты ошибаешься. Массивы существуют, и "x"является имеет lvalue. Распад массива и указателя очень плох, но они являются разные вещи. Функция void foo(int* a) принимает указатель на одно целое число. Тот факт, что этот указатель может указывать на элемент массива, будь он первым по убыванию указателя или любым другим способом, не имеет значения.

Puppy 01.05.2012 20:18

Любой, кто это видит, должен проголосовать против. Тонны и тонны ошибок здесь. Это распространенное заблуждение

std''OrgnlDave 01.05.2012 20:50

Хорошо всем. Первоначальный вопрос заключался в том, почему sizeof (arr) не возвращает размер массива. Думаю, я ответил на это.

Dima 01.05.2012 21:03

Еще я хочу сказать, что массивы C не являются объектами первого класса. Их размер должен быть указан во время компиляции, они должны быть размещены в стеке, и они распадаются на указатели. В большинстве практических целей, если вы хотите передать массив функции, вы должны передать указатель на первый элемент и размер массива. Даже если вы напишете библиотеку функций, работающих с векторами или матрицами фиксированного размера, вы все равно будете передавать указатели и размеры, потому что вы не можете ожидать, что вызывающий объект выделит все в стеке. См. Пример в «Числовых квитанциях».

Dima 01.05.2012 21:04

@Dima: Когда вы говорите «объект 1-го класса», что именно вы имеете в виду? Когда вы объявляете int a[4];, получается непрерывный 4-байтовый блок памяти. Что в этом отсутствует, что говорит о том, что это не массив? Я не пытаюсь наваливать, я пытаюсь получить разъяснения.

John Dibling 01.05.2012 21:20

@JohnDibling, объект первого класса - это сущность, которая может быть создана во время выполнения, передана как параметр, возвращена из подпрограммы или назначена переменной. en.wikipedia.org/wiki/First-class_object Из той же статьи: Во многих старых языках (например, C) массивы не были первоклассными: их нельзя было назначить как объекты или передать как параметр подпрограмме; только их элементами можно было напрямую манипулировать.

Dima 01.05.2012 21:50

@Dima: Значит, вы не утверждаете, что в C++ нет массивов, просто они не являются «объектами 1-го класса» по вашему определению?

John Dibling 01.05.2012 21:53

@JohnDibling, я сказал в ответ, что в C нет «настоящих» массивов, и позже я пояснил в комментариях, что имел в виду, что массивы C не являются объектами 1-го класса. И не по моему определению, а по общепринятому определению.

Dima 01.05.2012 21:56

@JohnDibling, например, сравнивает массивы C с массивами Java. Массивы Java могут быть назначены друг другу, они могут быть переданы в функции, и вам не нужно постоянно передавать вместе с ними их размер.

Dima 01.05.2012 21:58

@ Дима: Хорошо. Вы могли бы просто сказать «Да».

John Dibling 01.05.2012 21:59

@JohnDibling, и поскольку вы упомянули C++, то std :: vector фактически является «настоящим» массивом.

Dima 01.05.2012 22:02

@Dima "bar" - это lvalue, а &"bar" - действительный. Однако тип последнего - char (*)[4], который несовместим с char **.

M.M 12.01.2015 07:11

@Dima, я не думаю, что вы ответили на вопрос: «Если delete [] может получить доступ к размеру выделенного массива, почему не может программист». Извиняюсь, если я упустил суть.

Isaac Turner 03.06.2016 14:41

Краткий ответ: потому что так определяются новые / удаляемые. Длинный ответ: new / delete - это внутренняя бухгалтерия, скрытая от внешнего мира. То, как они это делают, намеренно не стандартизировано и зависит от компилятора. Одна из причин сокрытия этой информации заключается в том, что new () обычно выделяет больше памяти, чем вы запрашиваете, из-за выравнивания и из-за того, что он может использовать часть выделенной памяти для своей собственной бухгалтерии. Поскольку этот учет зависит от компилятора, ваш код не должен на него полагаться.

Dima 03.06.2016 18:40

По сути, вы не можете:

void foo(int* arr);

int arr[100] = {0};

foo(arr+1); // Calls foo with a pointer to 100-1 elements.

Массив C++ - это не что иное, как набор объектов, которые хранятся в непрерывной области памяти. Поскольку между ними нет дыр (заполнение - это объекты внутри), вы можете найти следующий элемент массива, просто вставив указатель. На уровне ЦП это простая настройка. C++ вставляет только множитель sizeof (element).

Обратите внимание, что реализации могут выбрать реализацию «жирных указателей», которые содержат границы массива. Они должны быть вдвое больше, так как вам нужно будет ссылаться на какой-то «дескриптор, связанный с массивом». В качестве побочного эффекта в таких реализациях вы можете вызвать delete [] (1+new int[5]);

К сожалению, это невозможно. В C и C++ обязанность программиста - помнить о длине массива, поскольку длина массива нигде не хранится. Delete [] и free () запоминают размер выделенного блока, но они могут выделить больше памяти, чем запрошено, поэтому их внутренние структуры данных, хранящие размеры выделенных блоков памяти, могут не дать вам точного размера вашего массива.

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

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

В качестве одного из примеров рассмотрим строку, реализованную как массив char *. Обычно для выделения подстрок используются указатели в середине массива. В качестве примера см. Функцию strtok в стандартной библиотеке C. Если бы какой-то заголовок нужно было встраивать непосредственно перед каждым массивом, вам нужно было бы удалить части массива перед подстрокой.

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

Шаблон std :: vector - мой любимый способ сохранить размер массива, привязанного к самому массиву.

C - переносимый язык ассемблера с улучшенным синтаксисом.

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

Mooing Duck 30.04.2012 10:22

В общем нет. Массивы в C и C++ - это просто блоки памяти без прикрепленной бухгалтерской информации. В общем случае это невозможно без сохранения длины массива в памяти и дополнительных накладных расходов.

Исключение составляют статические массивы. Например, если вы объявите: int a[50], то sizeof(a) будет работать. Это возможно, потому что [50] является частью статического типа массива: он известен компилятору. sizeof интерпретируется во время компиляции.

Однако, если вы создадите указатель: int *p = a, тогда sizeof(p) вернет размер указателя, как вы упомянули, а не размер массива, потому что компилятор не знает, на что указывает p.

C++ решил добавить new, чтобы сделать типизированный malloc, тогда new должен знать оба размера e номеров элементов для вызова ctors, поэтому delete для вызова dtors. В первые дни вы должны фактически передать, чтобы удалить числа, которые вы передали объектам new.

string* p = new string[5];
delete[5] p;

Однако они думали, что если использовать new <type> [], накладные расходы на число будут небольшими. Поэтому они решили, что new [n] должен запомнить n и передать его на удаление. Есть три основных способа его реализовать.

  1. вести хеш-таблицу указателя на размер
  2. написал это прямо рядом с вектором
  3. сделай что-нибудь совершенно другое

Возможно, удастся получить такой размер:

size_t* p = new size_t[10];
cout << p[-1] << endl;
// Or
cout << p[11] << endl;

Или, черт возьми, ничего из этого.

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

Когда вы это сделаете: int * arr = новый int [256];

256 не имеет значения, вам будет предоставлено 256 * sizeof (int), предполагая, что для этого случая 1024, это значение будет сохранено, вероятно, в (arr - 4)

Итак, чтобы дать вам количество "предметов"

int * p_iToSize = arr - 4;

printf ("Количество элементов% d", * p_iToSize / sizeof (int));

Для каждого malloc, new, независимо от того, что предшествует полученному вами блоку памяти Continos, также выделяется пространство, зарезервированное с некоторой информацией о блоке памяти, который вам был предоставлен.

Тем не менее, это действительно ответ на вопрос.

A. Rex 08.01.2009 23:41

интересно, :) в качестве дополнительных 2 цента вы можете перегрузить "новый" и реализовать управление памятью, как вам нравится, вы можете иметь его, как описывает Жоао, или хранить каждый указатель на карте с его соответствующим размером ... короче говоря, есть много безумных способов, но я бы не стал их использовать: p

chrispepper1989 23.05.2013 16:53

как насчет массива символов? char * arr = новый символ [100];

Jai 04.03.2014 11:58

Is there any way to determine the size of a C++ array programmatically? And if not, why?

  1. Нет, если вы сами не отслеживаете это.
  2. Потому что, если компилятору не нужно сообщать об этой информации никому, кроме себя, он меньше ограничивает компилятор. Желательно это или нет - вопрос спорный.

@ Дима,

How would the compiler know what the size of p is?

Компилятор должен знать размер p; в противном случае он не сможет реализовать delete[]. Компилятору не нужно никому сообщать, как он это выясняет.

Чтобы убедиться в этом, сравните указатель, возвращаемый operator new[], с указателем, возвращаемым new[].

Компилятор не может этого знать

char *ar = new char[100] 

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

Если вы хотите узнать размер данного массива, просто используйте std :: vector. std :: vector - это просто лучший массив.

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

char* chars=new char[100];
printf("%d",*((int*)chars-1));

Функция delete[] должна деконструировать все объекты в ней. для этого ключевое слово new[] помещает количество элементов за всем массивом.

Тело массива выглядит так:

int count;
ObjectType* data; //This value is returned when using new[]

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

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

Это нечетко и непрактично, но при этом мешает измерению. несерьезный ответ.

itsbruce 26.10.2012 19:36

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

ouflak 25.07.2013 13:54

В зависимости от вашего приложения вы можете создать «контрольное значение» в конце вашего массива.

У контрольного значения должно быть какое-то уникальное свойство.

Затем вы можете обработать массив (или выполнить линейный поиск) для контрольного значения, считая по мере продвижения. Как только вы достигнете значения дозорного, у вас будет счетчик массива.

Для простой строки C завершающий символ \ 0 является примером контрольного значения.

Немного волшебства:

template <typename T, size_t S>
inline
size_t array_size(const T (&v)[S]) 
{ 
    return S; 
}

И вот как мы это делаем в C++ 11:

template<typename T, size_t S>
constexpr 
auto array_size(const T (&)[S]) -> size_t
{ 
    return S; 
}

Очень полезное и красивое решение. Только одно: вместо этого я бы использовал size_t в качестве второго параметра шаблона.

besworland 10.08.2016 22:06

Теперь есть std :: array, эффективная оболочка времени компиляции вокруг массива постоянного размера:

#include <array>

int main (int argc, char** argv)
{
    std::array<int, 256> arr;
    printf("Size of arr: %ld\n", arr.size());
}

Параметры <type, #elements>.

Вы также получаете несколько других тонкостей, таких как итераторы, empty () и max_size ().

я делаю это путем деления размера массива на размер первого элемента

int intarray[100];
printf ("Size of the array %d\n", (sizeof(intarray) / sizeof(intarray[0]));

Он печатает 100

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

Mat 12.01.2015 06:07

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