Объявление массива int - PullRequest
62 голосов
/ 28 мая 2011

Есть ли разница между этими двумя объявлениями?

int x[10];

против

int* x = new int[10];

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

Ответы [ 8 ]

72 голосов
/ 28 мая 2011
#include<iostream>    

int y[10];


void doSomething()
{
    int x[10];
    int *z  = new int[10];
    //Do something interesting

    delete []z;
}

int main()
{
    doSomething();

}

int x[10]; 

- Создает массив целых чисел размера 10 в стеке.
- Вам не нужно явно удалятьэта память, потому что она уходит, поскольку стек разматывается.
- Область ее действия ограничена функцией doSomething()

int y[10];

- Создает массивцелые числа размера 10 в сегменте BSS / Data.
- Вам не нужно явно удалять эту память.
- Поскольку она объявлена ​​global, она доступна глобально.

int *z = new int[10];

- Распределяет динамический массив целых чисел размера 10 в куче и возвращает адрес этой памяти в z.
- Вы должны явно удалить эту динамическую память после ее использования.используя:

delete[] z;
8 голосов
/ 18 сентября 2017

Единственное, что похоже между

int x[10];

и

int* x = new int[10];

, - это то, что любой из них может использоваться в некоторых контекстах, где ожидается int*:

int* b = x;   // Either form of x will work

void foo(int* p) {}

foo(x);      // Either form will work

Однако их нельзя использовать во всех контекстах, где ожидается int*.В частности,

delete [] x;  // UB for the first case, necessary for the second case.

Некоторые основные различия были объяснены в других ответах.Другие основные различия:

Разница 1

sizeof(x) == sizeof(int)*10   // First case

sizeof(x) == sizeof(int*)     // Second case.

Разница 2

Тип &x is int (*)[10] в первом случае

Тип &x равен int** во втором случае

Разница 3

Заданная функция

void foo(int (&arr)[10]) { }

Вы можете позвонить, используя первый x, а не второй x.

foo(x);     // OK for first case, not OK for second case.
6 голосов
/ 20 сентября 2017

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

int x[10];

void method() {
     int y[10];
     int *z = new int[10];
     delete z;
}

В первом объявлении, int x[10], используется статическая продолжительность хранения, определяемая cppreference как: «Память для объекта выделяется, когда программа начинается, и освобождается, когда программа Заканчивается. Только один экземпляр объекта существует. Все объекты, объявленные в области пространства имен (включая глобальное пространство имен), имеют эту продолжительность хранения, плюс те, которые объявлены со static или extern. "

Второй, int y[10], использует автоматический срок хранения, определенный cppreference как: конец. Все локальные объекты имеют этот срок хранения, кроме объявленных static, extern или thread_local. "

Третий, int *z = new int[10], обычно упоминается как динамическое распределение памяти, и фактически представляет собой двухэтапную последовательность:

  • Во-первых, вызывается оператор new, который динамически распределяет память, используя либо методы выделения по умолчанию стандартной библиотеки, либо определяемую пользователем реализацию (поскольку new может быть переопределен во время выполнения). Выделенной памяти будет достаточно для размещения N выделенных элементов плюс любая дополнительная память, необходимая для хранения метаданных для данного выделения (чтобы впоследствии их можно было успешно освободить).
  • Во-вторых, если первый шаг успешен, мы затем приступаем к инициализации или созданию каждого объекта в массиве.

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

  1. В большинстве современных операционных систем:

    • автоматическое хранилище обычно выделяется в стеке, который (вообще говоря) является заранее выделенным пространством памяти для конкретного потока с использованием механизма LIFO
    • статическое хранилище использует предварительно выделенное пространство памяти, зарезервированное внутри исполняемого файла (, в частности, сегменты .BSS и .DATA, в зависимости от того, инициализирована ли переменная с нулем или нет )
    • динамическая память выделяется с использованием динамической памяти и зависит от системы управления ОЗУ системы и других механизмов, таких как подкачка.
  2. Динамически распределенная память должна быть явно delete -обработана программистом, тогда как статические и автоматические переменные хранения позаботятся "средой"

  3. Статические и автоматические переменные хранения ограничены определенной областью, тогда как динамически распределенная память не имеет границ, то есть переменная, объявленная в одном модуле, может быть передана любому другому модулю, работающему в том же адресном пространстве

  4. При выделении массива с использованием new[] размер может быть 0

  5. (Как уже указывал @R Sahu) Типы &x и &z различны:

    • &x is int (*)[10]
    • &z is int **
6 голосов
/ 28 мая 2011

Первый - это массив int размером 10.Сказать, что он создан в стеке, неправильно.Потому что Стандарт не гарантирует этого.Его реализация определяется.Его длительность хранения может быть статической или автоматической в зависимости от того, x является глобальной переменной или локальной переменной.

Во втором вы создаете указатель типа int*.Стандарт не обязательно создан в куче, это не сказано.Выделенная память занимает более 10 * sizeof(int) байтов.Для этого вам нужно освободить память самостоятельно, написав:

delete [] x; 

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

3 голосов
/ 21 сентября 2017

Декларации совершенно разные.

Первый случай,

int x[10];

объявляет x как массив 10 целых чисел, тогда как второй случай

int* x = new int[10];

объявляет x как указатель на int - переменную со значением, равным адресу int, и инициализирует этот указатель на результат нового выражения (new int [10]), которое динамически выделяет массив из десяти целых чисел.

Невзирая на различия, можно использовать аналогичным образом;

  • (например, x[i], где i - это целое значение между 0 и 9 включительно) может использоваться для установки или извлечения значений соответствующих массивов в вышеуказанном синтаксисе;
  • арифметику указателя можно использовать для получения адреса элемента массива (например, x + i эквивалентно &x[i] для i между 0 и 10 включительно. [Да, возможно получить адрес "один за концом"];
  • Разыменование указателя и доступ к массиву эквивалентны. то есть *(x+i) и x[i] эквивалентны, для i между 0 и 9 [разыменование указателя «один за концом» дает неопределенное поведение].

Однако есть и некоторые ключевые отличия, например;

Результаты оператора sizeof . sizeof(x) дает разные значения в двух случаях.

  1. В первом случае sizeof(x) == sizeof(int)*10. sizeof(int) дает определяемый реализацией баланс, но sizeof(x)/sizeof(*x) всегда будет давать количество элементов в массиве (то есть a std::size_t со значением 10).
  2. Во втором sizeof(x) == sizeof(int *) - это значение, определяемое реализацией. Значение sizeof(x)/sizeof(*x) практически невероятно маловероятно, чтобы получить значение 10. Это означает, что эту технику нельзя использовать для получения количества элементов.

Пожизненная .

  1. В первом случае время жизни x зависит от области, в которой происходит объявление. Если объявление происходит в области видимости файла (то есть в модуле компиляции, вне какого-либо функционального блока), тогда x имеет статическую продолжительность хранения (так существует до тех пор, пока работает программа). Если объявление происходит в блоке, x - и все его элементы - перестает существовать, когда блок заканчивается. Например

    {
        int x[10];
    
    }    //  x and all its elements cease to exist here
    
  2. Во втором случае только указатель x имеет время жизни, зависящее от области видимости. Динамически распределенная память (результат new x[10]) никогда не освобождается. Это означает, что время жизни x и время жизни (динамически распределяемого) массива, на который он ссылается, не связаны, что приводит нас к третьему различию .....

Результат присвоения Массив не может быть переназначен, указатель может (если не указано иное const квалифицирован).

Рассмотрим контекст

 // x as previously defined in one or the other form

 int y[10];
 int z;

 x = y;
 x = &z;

В первом случае оба назначения приведут к диагностике компилятора - назначения недействительны. Во втором случае назначения действительны и заставляют x указывать по адресу (первый элемент) y и по адресу z соответственно. Если значение x не будет сохранено в другом указателе перед переназначением, память, выделенная новым выражением (new int [10]), будет утечка - она ​​больше не доступна для программы, но также не освобождается.

0 голосов
/ 11 февраля 2018

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

void doSomething(int size)
{
    int x[size];               // does not compile
    int *z  = new int[size];
    //Do something interesting ...
    doMore(z, size);
}

, то x не будет компилироваться в C ++, поэтому вы должны использовать z.Хорошей новостью является то, что теперь вы можете использовать z, в большинстве случаев, как если бы он был статически распределен, например:

void doMore(int anArray[], int size)
{
    // ...
}

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

0 голосов
/ 23 сентября 2017

Они одинаковы настолько, насколько оба x указывают на первый адрес памяти в массиве из 10 целых чисел, однако сильно отличаются в этом

int x[10] 

объявляет память в статической оперативной памяти, а Ключевое слово «new» создает их динамически с кучей, и примерно так же, как использование malloc в c для динамического создания массива.

Не только это, но (я полагаю, не проверял теорию) есть вероятность, что:

int* x = new int[10];

может завершиться ошибкой и, в зависимости от компилятора, может вернуть ошибку или нулевой указатель. Если компилятор c ++ придерживается стандартов ANSI / ISO, то он поддерживает форму new "no-throw", которая возвращает нулевое значение в случае неудачного размещения, а не генерирует исключение.

Другое отличие состоит в том, что оператор 'new' может быть перегружен.

Однако в чем я не уверен, так это в том случае, если один из них (в c ++) создает массив с нулевым символом в конце. Я знаю, что в c, по крайней мере, в компиляторе, который я использую, вы должны всегда добавлять \ 0 к любым строкам или массивам, если вы ожидаете, что сможете перебирать их без перерасширения границ.

Только моя стоимость в $ .02. :)

0 голосов
/ 28 мая 2011

Первый случай: x создается в сегменте стека / данных в зависимости от того, является ли это нестатической локальной переменной или статической / глобальной переменной. И адрес x не может быть изменен.

Второй случай: «x» - указатель, указывающий на массив, созданный , как правило, в куче (свободное хранилище). Вы также можете изменить x, указывая на что-то еще. Более того, вам нужно позаботиться о его освобождении, используя delete[] x;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...