Можно ли обращаться к многомерным массивам как к одномерным?
Насколько я помню, в C можно без проблем обращаться к многомерному массиву(NxM) по указателю: a[i*M+j].
Доступна ли такая опция в C++?
В качестве примера приведу мой ответ, который и заставил меня задуматься об этом: https://ru.stackoverflow.com/a/1197648/176064
for (int index1 = 0; index1 < N*M; ++index1) {
int i = index1 / M;
int j = index1 % M;
if (A[i][j] >= 0) { // A[0][index1] ?
// ...
Интересует, можно ли в нём избавиться от вычисления подындексов(деления) или лучше не надо?
Если тут UB, оптимизатор имеет полное право превратить цикл в бесконечный(полагая, что всегда index1 < M)
А если нет, можно было бы избежать и деления, и глупых ошибок(которую я таки допустил), как деление на N вместо M =)
Ответы (1 шт):
В C/C++ есть только одномерные массивы. Но элементами одномерного массива помимо всего прочего также могут быть массивы. Таким образом N-мерный массив — это одномерные массив, элементами которого являются N-1 мерные массивы.
[Note 1: An array can be constructed from one of the fundamental types (except void), from a pointer, from a pointer to member, from a class, from an enumeration type, or from an array of known bound. — end note]
Также смотрите dcl.array/example-4:
int x3d[3][5][7];
declares an array of three elements, each of which is an array of five elements, each of which is an array of seven integers.
int x3d[3][5][7];
объявляет массив из трёх элементов, каждый из которых является массивом из пяти элементов, каждый из которых является массивом из семи элементов типаint.
Между элементами массива нет никаких "пропусков". Непосредственно за i-тым элементом массива следует i+1-ый. dcl.array/6:
An object of type “array of
NU” consists of a contiguously allocated non-empty set ofNsubobjects of typeU, known as the elements of the array, and numbered0toN-1.
Таким образом, даже если элементами массива являются массивы, они расположены в памяти непрерывно один за другим. Оператор sizeof, применённый к массиву, возвращает размер элемента массива, умноженный на количество элементов в массиве. Никакой возможности вставить лишний байт между элементами массива просто нет. expr.sizeof/2:
... When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of
nelements isntimes the size of an element.
Теперь рассмотрим код:
int arr[2][10];
int* p = &arr[0][0];
p = p + 11//UB???
После строки int* p = &arr[0][0]; указатель p указывает на первый элемент массива из десяти элементов типа int.
После строки p = p + 11; указатель p вроде бы должен указывать на второй элемент другого массива из десяти элементов (мы ведь знаем, что наши массивы расположены подряд), но с точки зрения стандарта языка — это неопределённое поведение. Наращивать/уменьшать указатель на элемент массива можно в строго определённых пределах. expr.add/4:
When an expression
Jthat has integral type is added to or subtracted from an expressionPof pointer type, the result has the type ofP.— If
Pevaluates to a null pointer value andJevaluates to0, the result is a null pointer value.— Otherwise, if
Ppoints to an array elementiof an array objectxwithnelements ([dcl.array]),80) the expressionsP + JandJ + P(whereJhas the valuej) point to the (possibly-hypothetical) array elementi+jofxif0≤i+j≤nand the expressionP - Jpoints to the (possibly-hypothetical) array elementi−jofxif0≤i−j≤n.— Otherwise, the behavior is undefined.
Как видите, никаких послаблений для многомерных массивов здесь нет.
Если очень хочется работать с N-мерным массивом как с одномерным, и при этом очень сильно хочется не нарушать стандарт языка, то наверное (я не уверен наверняка :) ), можно проделать такой трюк:
const int rows = 2;
const int cols = 10;
int arr[rows][cols];
unsigned char* p = reinterpret_cast<unsigned char*>(&arr[0][0]);
for (int i = 0; i < rows * cols; ++i)
reinterpret_cast<int&>( p[i * sizeof(int)] ) = some_value;
Здесь мы рассматриваем двумерный массив, как одномерный массив объектов типа unsigned char. Так делать можно потому, что любой объект типа T также является массивом из sizeof(T) элементов типа unsigned char. basic.types.general/4:
The object representation of an object of type
Tis the sequence ofNunsigned charobjects taken up by the object of typeT, whereNequalssizeof(T).
Ни нарушения требований по выравниванию, ни нарушения strict aliasing в приведённом коде, насколько я понимаю стандарт, не происходит. Но выглядит это всё как-то неэстетично :)
P.S. На мой взгляд, UB при доступе к N-мерному массиву как одномерному — это просто недоработка/баг в стандарте. Ибо запрещать выходить за границы подмассивов массива — какое-то насилие над здравым смыслом :) К тому же похоже, что все существующие компиляторы обрабатывают данное UB одинаково: ничего не "ломается" при оптимизации и работает как и задумано.
P.P.S. В языке C выход за границы подмассива массива также приводит к неопределённому поведению.
P.P.P.S. У функций memcpy(), memset() нет проблем с обработкой многомерных массивов, ибо переданный им указатель на объект любого типа они рассматривают как указатель на первый элемент массива с элементами типа unsigned char.