Динамическе и статические n-мерные массивы в С++

147
16 августа 2018, 23:20

Похоже я не совсем понимаю что из себя представляют двумерные (n-мерные) массивы в C++, чем они являются для оперативной памяти и чем двумерный массив объявленный на стеке вот таким образом:

RGBQUAD frameBuffer[300][300];

отличается от динамически объявленного двумерного массива:

RGBQUAD ** frame = new RGBQUAD*[height];
for (uint32_t i = 0; i < height; i++)
{
    frame[i] = new RGBQUAD[width];
    std::fill_n(frame[i], width, clearColor);
}

Теперь немного о самой задаче

Решил я попробовать написать что-то вроде software-renderera, начать решил с самого базового - точки (а именно вывода ее на экран). В WinAPI конечно же есть средства для рисования и установки цвета отдельных пикселей, но после небольшого ковыряния данной темы, я понял что правильным подходом будет именно подготовка кадра как bitmap картинки, а затем показ данной картинки на окне. То есть по задумке у меня будет формироваться bitmap, который я буду с определенной частотой показывать на окне, таким образом получив что-то вроде анимации. Сам по себе показ картинки у меня осуществляется примерно вот так:

// Cоздать временный bitmap (4 байта на пиксель), последний параметр типа void*
// Это может быть как одномерный так и двумерный массив структур RGBQUAD
HBITMAP hBitMap = CreateBitmap(width, height, 1, 8 * 4, pixels);
// Получить device context окна
HDC hdc = GetDC(hWnd);
// Временный DC для переноса bit-map'а
HDC srcHdc = CreateCompatibleDC(hdc);
// Связать bit-map с временным DC
SelectObject(srcHdc, hBitMap);
// Копировать содержимое временного DC в DC окна
BitBlt(
    hdc,    // HDC назначения
    0,      // Начало вставки по оси X
    0,      // Начало вставки по оси Y
    width,  // Ширина
    height, // Высота
    srcHdc, // Исходный HDC (из которого будут копироваться данные)
    0,      // Начало считывания по оси X
    0,      // Начало считывания по оси Y
    SRCCOPY // Копировать
);
// Уничтожить bit-map
DeleteObject(hBitMap);
// Уничтожить временный DC
DeleteDC(srcHdc);
// Уничтожить DC
DeleteDC(hdc);

Все нормально работало, причем удивительно что работало как с одномерным, так и двумерным массивом структур, в качестве параметра pixels (в функции CreateBitmap). Будто бы двумерные массивы в С++, которые объявлены не динамически, воспринимаются как одномерные.. И тут я попробовал выделить этот самый фрейм-буфер (двумерный массив) динамически, в куче, то есть по сути создать массив указателей на массивы (выше пример, как именно я это делал). И в итоге все перестало работать, теперь функция CreateBitmap этого не понимает. Я попытался тогда динамически выделить одномерный массив, как-то вот так:

RGBQUAD * frame = new RGBQUAD[width * height];
std::fill_n(frame, width * height, clearColor);

И все тут же стало работать.

Вопрос: в чем дело? Что я упускаю? Действительно ли в С++ двумерные массивы объявленные динамически это совсем не то же самое что двумерные массивы объявленные обычным способом? В чем разница? За ранее спасибо.

Answer 1

Для обычных массивов, вроде int a[3] и int b[3][3], в памяти хранятся только сами элементы подряд, и все.

int a[3]
.------.------.------.------.------.
| a[0] | a[1] | a[2] | a[3] | a[4] |
'------'------'------'------'------'
int b[3][2]
.---------.---------.---------.---------.---------.---------.
| b[0][0] | b[0][1] | b[1][0] | b[1][1] | b[2][0] | b[2][1] | 
'---------'---------'---------'---------'---------'---------'

А вот так выглядит одномерный массив, созданный через new. Указатель на первый элемент хранится отдельно.

int *c = new int[5]
.-----.
|  c  |
'--.--'
   |
   |
   V
.-----.-----.-----.-----.-----.
| [0] | [1] | [2] | [3] | [4] |
'-----'-----'-----'-----'-----'

Для многомерного массива вы использовали массив указателей на массивы, он же "рваный" массив.

int **d = new int*[3];
for (int i = 0; i < 3; i++)
    d[i] = new int[4];
.-----.
|  d  |
'--.--'
   |
   |
   V
.-----.-----.-----.
| [0] | [1] | [2] |
'--.--'--.--'--.--'
   |     |     |        .--------.--------.--------.--------.
   |     |     '------> | [2][0] | [2][1] | [2][2] | [2][3] |
   |     |              '--------'--------'--------'--------'
   |     |        .--------.--------.--------.--------.
   |     '------> | [1][0] | [1][1] | [1][2] | [1][3] |
   |              '--------'--------'--------'--------'
   |        .--------.--------.--------.--------.
   '------> | [0][0] | [0][1] | [0][2] | [0][3] |
            '--------'--------'--------'--------'

В таком случае строчки не обязательно расположены в памяти подряд, в отличие от массива, созданного обычным способом, вроде int f[3][4].

Самый удобный (мне кажется) способ сделать так, чтобы строчки шли в памяти подряд, вы уже нашли: Использовать одномерный массив: int *g = new int[w * h];.

Если хотите использовать удобный синтаксис для доступа к элементам: g[y][x] вместо g[y * w + x], то лучше всего сделать свой класс двухмерного массива с перегруженным оператором [].

Кстати, очень советую вместо new и delete использовать std::vector.

С new/delete легко забыть освободить память, и получить утечку. Замучаетесь искать потом.

Особенно легко забыть, если у вас в коде что-то выбросит исключение.

А std::vector следит за освобождением памяти за вас.

Answer 2

Разница в том что статический двумерный массив ничем не отличается от одномерного, Значит расположен он будет так (пример двумерного статического массива 5x5) на схеме обозначает ячейку

ロロロロロ ロロロロロ ロロロロロ

А в динамическом двумерном массиве все первые элементы это указатели, и расположены все элементы в рознь по оперативной памяти (пример двумерного динамического массива 5x5) на схеме & обозначает ссылку

&1 ---> ロロロロロ
&2 ---> ロロロロロ
&3 ---> ロロロロロ
&4 ---> ロロロロロ
&5 ---> ロロロロロ

Соответственно они разбросаны по оперативной памяти хаотично в отличии от статического двумерного массива

Если вам нужен непрерывный двумерный массив, есть вариант

int arr = new int[w*h];
READ ALSO
time: идентификатор не найден (ctime подрубил)

time: идентификатор не найден (ctime подрубил)

Начал учить C++ , написал маленький код

146
C++ утечка памяти c DPAPI CryptUnprotectData

C++ утечка памяти c DPAPI CryptUnprotectData

Пытаюсь реализовать функцию дешифрования из DPAPIПозже я заметил, что после моей функции в памяти забивается 8

135
Test project не выполняет test case

Test project не выполняет test case

Создал юнит-тест c помощью Boost Test

154
Вопрос по лямбдам в Java, функции map, filter

Вопрос по лямбдам в Java, функции map, filter

Пытаюсь переделать данные методы в stream, используя filter и map методы

478