Страницы

Поиск по вопросам

суббота, 11 января 2020 г.

Что если обернуть тип массива в скобки при выделении памяти?

#cpp #language_lawyer #delete


Стандартный способ выделить массив из 5 элементов - так:

int *a = new int[5];


ну и удаляется он

delete[] a;


А что если добавить скобки?

int *b = new (int[5]);


Круглые скобки заставляют выделять один объект, являющийся массивом из 5 элементов,
а не массив из 5 элементов. Это подтверждается сообщением об ошибке при попытке подставить
переменную в размер массива (где d=):

http://codepad.org/BvHyTPs6

#include 

using namespace std;

int main()
{
  int *a = new int[5];
  int *b = new (int[5]);

  int n = 5;
  int *c = new int[n];
  int *d = new (int[n]);

  return 0;
}



In function 'int main()':
Line 12: error: ISO C++ forbids variable-size array
compilation terminated due to -Wfatal-errors.



Чем массив b отличается от массива a и как правильно освободить за собой память,
выделенную таким образом?

delete b;


или всё-таки

delete[] b;


и почему?

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

Кстати, на ideone аналогичный код компилируется, как и обычный variable-size array:
https://ideone.com/MGmBya. Этот факт как-то влияет на освобождение памяти?



Также выяснилось, что компилятор не хочет выводить константность размера из такого
объявления https://ideone.com/XwtTMQ & http://codepad.org/p3VKqMvd

#include 

using namespace std;

template  void f(int a[n])
{
  cout << n << endl;
}

int main()
{
  f(new (int[5]));
  return 0;
}



prog.cpp: In function ‘int main()’:
prog.cpp:12:17: error: no matching function for call to ‘f(int*)’
   f(new (int[5]));
                 ^
prog.cpp:5:26: note: candidate: ‘template void f(int*)’
 template  void f(int a[n])
                          ^
prog.cpp:5:26: note:   template argument deduction/substitution failed:
prog.cpp:12:17: note:   couldn't deduce template parameter ‘n’
   f(new (int[5]));
                 ^



Тогда возникает вопрос, почему та же конструкция с переменной - это VLA, а с константой
- простой указатель.


    


Ответы

Ответ 1



Ключевой момент, как уже заметил @VTT в комментариях: Круглые скобки заставляют выделять один объект, являющийся массивом из 5 элементов, а не массив из 5 элементов. это неправда. #include int main() { int ptr[5] = {0, 1, 2, 3, 4}; auto p1 = new (ptr)(int[5]); auto p2 = new (int[5]); std::cout << p1[3] << std::endl; delete []p2; // ok delete []p1; // error return 0; } new expression - вариант синтаксиса (1), с указанием типа в скобках То есть вы по прежнему выделяете массив из 5 элементов. ::(optional) new (placement_params)(optional) ( type ) initializer(optional) вот описание синтаксиса new expression, собcтвенно вы указываете тип в скобках. Вот здесь: auto p2 = new (int[5]); Типом будет массив из 5 элементов. Это то же самое что написать auto p2 = new int[5]; только это уже второй вариант синтаксиса, когда тип указывается без скобок. И во втором варианте (без скобок) вы можете в качестве первой размерности задавать переменную, которую можно привести к std::size_t, а в первом варианте так делать уже нельзя. То есть можно: int n = 5; auto p = new int[n][5]; Но нельзя int n = 5; auto p = new int[5][n]; auto p1 = new (int[n][5]); If type is an array type, all dimensions other than the first must be specified as positive integral constant expression (until C++14)converted constant expression of type std::size_t (since C++14), but (only when using un-parenthesized syntax (2)) the first dimension may be any expression convertible to std::size_t. This is the only way to directly create an array with size defined at runtime, such arrays are often referred to as dynamic arrays

Ответ 2



Компилятор смотрит тип предоставленный оператору new и если этот тип является массивом, то возвращаемое значение будет указателем на первый элемент. int * ip = new int[1]; ip имеет тип указателя на int, а тип int[1] - это уже массив. Второй случай, где оператору new предоставлен тип не массив: char * cp = new char ; Здесь cp это указатель на char, а аргумент оператору тоже char. Чтобы определить, как удалять память нужно сравнить типы *указатель и аргумент new. При сложном случае, когда типы неизвестны (typedef или шаблон) можно воспользоваться сравнением типа с помощью typeid. // g++ -Wall -Wextra -Wpedantic -Os -std=c++11 newarr3.cpp #include #include using namespace std; class C{ public: int i; C(){cout<<"C;";} ~C(){cout<<"~C;";} }; typedef C A[5]; typedef C B[1] ; typedef C D ; int main() { cout<<"auto ap = new A :"< "<< "typeid(A).name = "< "<< "typeid(B).name = "< "<< "typeid(D).name = "< typeid(A).name = A5_1C ~C;~C;~C;~C;~C; C; typeid(*bp).name = 1C <> typeid(B).name = A1_1C ~C; C; typeid(*dp).name == typeid(D).name == 1C ~C; В вашем примере однозначно видны типы. Возвращаемый тип это указатель на int, а память выделяется на тип массива. Удалять нужно с помощью delete[].

Комментариев нет:

Отправить комментарий