Вернуть массив из функции

Здравствуйте.
Пытаюсь сделать следующее.

double* create()
{
    double arr[16]{ 0.0 };
    for (int i = 0; i < 16; ++i) { arr[15 - i] = (double)i; }

    return &arr[0]; //или return (double*)&arr;
}

int main()
{
    double* a = create();
    for (int i = 0; i < 16; ++i)
    {
        std::cout << a[i] << std::endl;
    }

Если поставить точку останова на строчку 511


то видно число 15. Значит массив (ну точнее, указатель) вернулся правильно.

Теперь, если перейти на следующую строчку (F10), то видим уже какую-то фигню:

Видимо, массив уничтожился и указатель теперь ссылается на мусор.
Но при этом :point_up: Если передать этот массив в OpenGL, вот так:

while (running)
{
...
    double* m = createProjectionMatrix(fov, aspect, zNear, zFar);
    glLoadMatrixd(m);
...
}

то всё работает. Сцена рендерится. Почему? :thinking:
А если сделать нормально - выделить под массив память при помощи оператора new (ну или через функцию _malloca) и вернуть этот указатель. Тогда перебирается нормально. Но потом требуется уничтожить память оператором delete[].
Вопрос в том, почему без явного выделения памяти проиходит то, что происходит?

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

Элементы массива тоже в этом случае размещаются (компилятором) в стеке, как будто бы была использована функция alloca

А тот указатель, который возвращает функция из OpenGL, он указывает на динамическую память (выделенную при помощи malloc). Динамическая память, она же “куча”, это не стек, и при выходе из функции с ней ничего не происходит.

Во втором примере, уничтожение массива не происходит, потому что переменная массива ещё не выходит из области видимости.

Это понятно.

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

Второй пример это про OpenGL?’ Почему не выходит? Мы ведь уже вышли из функции createProjectionMatrix(), также как в первом примере вышли из create().
В чём разница?

Мы не видим текст функции createProjectionMatrix, и не знаем, как она выделяет память. Но может быть так, что данные на стеке не затираются. Случайно повезло.

double* makeFrustum(double left, double right, double bottom, double top, double zNear, double zFar)
{
	double deltaW = right - left;
	double deltaH = top - bottom;
	double deltaZ = zFar - zNear;
	double twoZNear = zNear * 2.0;

	double matrix[16]{ 0.0 };
	matrix[0] = twoZNear / deltaW;
	matrix[5] = twoZNear / deltaH;
	matrix[8] = (left + right) / deltaW;
	matrix[9] = (bottom + top) / deltaH;
	matrix[10] = (-zFar - zNear) / deltaZ;
	matrix[11] = -1.0;
	matrix[14] = (-twoZNear * zFar) / deltaZ;

	return (double*)&matrix; //или return &matrix[0];
}

double* createProjectionMatrix(double fov, double aspect, double zNear, double zFar)
{
	double yMax = zNear * tan(fov * M_PI / 360.0);
	double xMax = yMax * aspect;
	return makeFrustum(-xMax, xMax, -yMax, yMax, zNear, zFar);
}

Не вижу разницы :man_shrugging:

ну, так делать не надо, вот и всё…

Я же передаю туда указатель на переменную стека, которая, по-идее, уже уничтожена.

Ты вставь во втором примере между двумя вызовами ещё один. Вызови вспомогательную функцию, в которой выдели массив и перезапиши его содержимое. И тогда содержимое станет уничтожено не “по-идее”, а фактически.
Оптимизацию только надо в компиляторе подавить, чтобы не сработала и не удалила неиспользуемую переменную.

void useTheStackAdditionally()
{
    double arrayWhichElementsAreZeroedAtCreation[1000] = {0};
}
...
    double* m = createProjectionMatrix(fov, aspect, zNear, zFar);
    useTheStackAdditionally();
    glLoadMatrixd(m);