0

Знаю, тема изьезжена. Но все же возник вопрос:

Известно, что в Си строго не рекомендуется возвращать сырой массив из функции, поскольку функция вернёт не массив, а указатель на него, и потом могут возникнуть проблемы с удалением выделенной памяти: нет смысла применять delete к указателю. Но это же указатель на массив. Почему бы тогда не применить delete[]?

#include <iostream> 

int *getArray() 
{ 
 int* tab = new int[30]; 
 return tab; 
} 

int main() 
{ 
 int* arrayFromFunction = getArray(); 
 std::cout << arrayFromFunction[0]; 
 delete [] arrayFromFunction; 
}

Соответствует ли это стандарту? Есть люди, которые утверждают, что этот вариант вполне рабочий. Можно ли им доверять?

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
Александр
  • 1,978
  • 1
  • 16
  • 25
  • Единственная проблема, которая может возникнуть - вы забудете вызать delete[]. "функция вернёт не массив, а указатель на него" А при чем тут функция? tab остается указателем (не массивом) даже если не возвращать его из getArray. – HolyBlackCat Dec 27 '19 at 17:19
  • Ни в С, ни в С++ возвратить из функции массив нельзя в принципе, в языке просто нет такого синтаксиса. Возвращать указатель - пожалуйста. – user7860670 Dec 27 '19 at 17:37
  • @HolyBlackCat , ну да. Вы правы. Указатель и там, и там. Просто из функции вернётся указатель на тип int, а не на массив. Но этот факт мне нисколько не позволяет понять, как сработает к нему delete[] – Александр Dec 27 '19 at 17:42
  • @Harry , то есть такая конструкция валидна? – Александр Dec 27 '19 at 17:45
  • 1
    "из функции вернётся указатель на тип int, а не на массив" Не понял. Даже если никуда не возвращать int* tab = new int[30];, это все равно 'указатель на тип int'. Указатель с типом "указатель на массив" выглядел бы так: int (*tab)[N];. – HolyBlackCat Dec 27 '19 at 17:50
  • Да, конечно, все корректно. Вы выделили память с помощью new[], освободили с помощью delete[], все в рамках одного менеджера памяти - так что тут все нормально. – Harry Dec 27 '19 at 18:02
  • "Известно, что в Си строго не рекомендуется возвращать сырой массив из функции", а откуда вы это взяли? я ещё могу понять подобное в c++ ввиду умных указателей, но в си... – Eikthyrnir Dec 27 '19 at 19:04
  • @timbars , ну вроде как рекомендуют очищать память там, где выделили (внутри одного метода) – Александр Dec 27 '19 at 19:07
  • @Александр да с чего вы это взяли? вот вам самый тупой пример: malloc – Eikthyrnir Dec 27 '19 at 19:28
  • 2
    Кхм, а не путаете ли вы выделение памяти в библиотеке, а освобождение в приложении? Так да, так делать нельзя, если память выделяла библиотека, то она и должна освобождать эту память, т.е. new_some <-> free_some, но никак мне вызов delete / free в приложении которое получило этот указатель на массив, или объект. –  Dec 27 '19 at 22:14

2 Answers2

2

Проблема здесь только в том, что во времена, когда космические корабли бороздят просторы C++17 и уже подлетают к системе C++20, вы занимаетесь ручным управлением памятью.

Что переменная tab, что arrayFromFunction, обе они имеют тип int*. Да, такой указатель может указывать как на скалярное значение, так и на массив оных. Именно поэтому и возможны вообще ошибки неправильного использования delete / delete[]. При освобождении памяти всегда надо знать, что лежит по указанному адресу. В вашем случае по адресу всегда размещается массив (выделен через new[]), стало быть и удалять его надо через delete[], а не delete. Иначе схватите неопределённое поведение.

По вашему коду представьте, что функция просто вставилась (inline) в место вызова (перестала существовать как вызываемая функция), тогда:

int* arrayFromFunction = getArray(); 
std::cout << arrayFromFunction[0]; 
delete [] arrayFromFunction; 

превратится в:

int* arrayFromFunction = new int[30]; 
std::cout << arrayFromFunction[0]; 
delete [] arrayFromFunction; 

Здесь, я думаю, у вас уже не возникает вопроса в валидности кода. Фактически эти куски кода дают идентичный результат.

Но вот если бы ваша функция getArray превратилась в нечто такое:

int* getSome(bool c) 
{ 
    if (c) {
        int* tab = new int[30]; 
        return tab; 
    }
    return new int(42);
} 

То в зависимости от условия c возвращался бы либо массив, либо одиночное число. И для всего этого пришлось бы ставить в соответствие правильный вызов delete или delete[], чтобы программа оставалась корректной.

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
  • Спасибо за развернутый ответ. Я, кажется, понял, что меня ещё в ней смущало. Получается, что, вернув таким образом указатель на массив, я не смогу программно извне узнать количество выделенных в нем элементов через sizeof: sizeof вернёт лишь размер указателя (либо первого элемента при разыменовывании). А следовательно я не буду знать границы массива. А не зная его края, не смогу быть уверенным, что вне функции не выйду за границы диапазона при обращении к его элементам... То есть как минимум нужно ещё передать в функцию дополнительный параметр, который будет выносить во вне размер. Я прав? – Александр Dec 30 '19 at 17:37
  • 1
    @Александр именно поэтому все функции, работающие с сырыми указателями дополнительно принимают параметр, задающий размер. Если, конечно, не ожидается иной признак окончания последовательности, например, нулевой символ с строках. – αλεχολυτ Dec 30 '19 at 17:43
1

Верить можно.

Ибо как написано в самой документации:

Для объектов, не имеющих тип класса (класс, Структураили объединение), вызывается оператор глобального удаления. Для объектов типа класса имя функции освобождения разрешается в глобальной области, если выражение delete начинается с оператора унарного разрешения области (::). В противном случае перед освобождением памяти оператор удаления вызывает деструктор объекта (если указатель не имеет значения null). Оператор удаления можно определять отдельно для каждого класса; если для некоторого класса такое определение отсутствует, вызывается глобальный оператор удаления. Если выражение удаления используется для освобождения объекта класса, статический тип которого имеет виртуальный деструктор, функция освобождение разрешается через виртуальный деструктор динамического типа объекта.

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

Функция лишь вернет указатель на определенное место в памяти. И ничем плохим это не грозит.

  • Но ничего страшного, что delete будет применен к этому указателю, который неизвестно, в какой области памяти находится? – Александр Dec 27 '19 at 17:37
  • То есть вернётся не указатель на массив, а указатель на тип int – Александр Dec 27 '19 at 17:49
  • А почему будет неизвестно, на какой участок памяти он указывает? – Vladimir Afanasyev Dec 27 '19 at 19:31