Подтвердить что ты не робот

Можно ли рассматривать 2D-массив как непрерывный массив 1D?

Рассмотрим следующий код:

int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;

Влияет ли вторая строка на поведение undefined? Как насчет четвертой строки?

4b9b3361

Ответ 1

Это до интерпретации. В то время как требования к смежности массивов не оставляют много для воображения с точки зрения того, как размещать многомерные массивы (это было указано ранее), обратите внимание, что когда вы делаете p[1234], вы индексируете 1234-й элемент нулевой ряд из 80 столбцов. Некоторые интерпретируют единственные допустимые индексы как 0..79 (&p[80] - частный случай).

Информация из C FAQ, которая представляет собой собранную мудрость Usenet по вопросам, относящимся к C. (Я не думаю, что C и С++ отличаются это важно, и это очень важно.)

Ответ 2

Да, вы можете (нет, это не UB), это косвенно гарантируется стандартом. Вот как: 2D-массив представляет собой массив массивов. Гарантируется, что массив имеет непрерывную память, а sizeof(array) - sizeof(elem) умножает количество элементов. Из этого следует, что то, что вы пытаетесь сделать, совершенно законно.

Ответ 3

Обе линии действительно приводит к непредсказуемому поведению.

Подписка интерпретируется как сложение указателя, за которым следует косвенное указание, то есть a[0][1234]/p[1234] эквивалентно *(a[0] + 1234)/*(p + 1234). Согласно [expr.add]/4 (здесь я цитирую новейший черновик, хотя на данный момент предлагается OP, вы можете сослаться на этот комментарий, и вывод такой же):

Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно, гипотетический) элемент x [i + j] если 0≤i + j≤n; в противном случае поведение не определено.

поскольку a[0] (с указателем на a[0][0])/p указывает на элемент из a[0] (в виде массива), а a[0] имеет только размер 80, поведение не определено,

Ответ 4

На языке, который был написан для описания, не было бы проблем с вызовом такой функции, как:

void print_array(double *d, int rows, int cols)
{
  int r,c;
  for (r = 0; r < rows; r++)
  {
    printf("%4d: ", r);
    for (c = 0; c < cols; c++)
      printf("%10.4f ", d[r*cols+c]);
    printf("\n");
  }
}

на double[10][4] или double[50][40], или на любой другой размер, при условии, что общее количество элементов в массиве было меньше, чем rows*cols.

С другой стороны, Стандарт признал, что когда реализациям дается что-то вроде:

double d[10][10];
double test(int i)
{
  d[1][0] = 1.0;
  d[0][i] = 2.0;
  return d[1][0]; 
}

позволяя им генерировать код, который предполагал бы, что d[1][0] будет по-прежнему держать 1.0, когда выполняется return, или позволял им генерировать код, который перехватывал бы, если i больше 10, позволил бы им быть более подходящими для некоторых целей, которые требуют, чтобы они молча возвращали 2.0 если вызывается с i==10.

Ничто в Стандарте не делает различий между этими сценариями. Хотя для Стандарта было бы возможно включить правила, которые бы говорили, что второй пример вызывает UB, если i >= 10 не затрагивая первый пример (например, скажем, что применение [N] к массиву не приводит к его затуханию). к указателю, но вместо этого выдает N-й элемент, который должен существовать в этом массиве), вместо этого стандарт опирается на тот факт, что реализациям разрешается вести себя полезным образом, даже если это не требуется, и авторы компилятора должны, вероятно, быть способными распознавания ситуаций, подобных первому примеру, когда это пойдет на пользу их клиентам.

Так как Стандарт никогда не стремился полностью определить все, что программистам нужно будет делать с массивами, не следует искать руководство относительно того, какие конструкции должны поддерживать реализации качества.

Ответ 5

Вы можете свободно переосмысливать память по своему усмотрению. Пока множественность не превышает линейную память. Вы можете даже перемещать a до 12, 40 и использовать отрицательные индексы.

Ответ 6

Ваш компилятор выкинет кучу предупреждений/ошибок из-за индексации вне диапазона (строка 2) и типов несовместимости (строка 3), но до тех пор, пока фактическая переменная (int в этом случае) является одной из внутренней базы Эти типы сохраняются в C и С++. (Если переменная является классом/структурой, она, вероятно, все еще будет работать на C, но на С++ все ставки отключены.)

Почему вы хотели бы это сделать.... Для 1-го варианта: если ваш код опирается на этот беспорядок, он будет подвержен ошибкам и будет трудно поддерживать в конечном итоге.

Я вижу некоторое использование для второго варианта, когда оптимизация производительности выполняется над 2D-массивами путем замены их на 1D-указатель на пространство данных, но хороший оптимизирующий компилятор часто это делает сам по себе. Если тело цикла является настолько большим/сложным, что компилятор не может оптимизировать/заменить цикл на 1-й прогон на своем собственном уровне, прирост производительности при его использовании вручную, скорее всего, также не будет значительным.

Ответ 7

Память, на которую ссылается a, является как int[25][80], так и int[2000]. Так говорит стандарт, 3.8p2:

[Примечание: время жизни объекта массива начинается, как только будет получено хранилище с надлежащим размером и выравниванием, а его время жизни заканчивается, когда хранилище, которое занимает массив, повторно используется или освобождается. 12.6.2 описывает время жизни базовых и членных подобъектов. - конечная нота]

a имеет конкретный тип, это lvalue типа int[25][80]. Но p - это просто int*. Это не "int*, указывающее на int[80]" или что-то в этом роде. Таким образом, на самом деле обозначенный int представляет собой элемент int[25][80] с именем a, а также элемент int[2000], занимающий одно и то же пространство.

Так как p и p+1234 - оба элемента одного и того же объекта int[2000], то арифметика указателя хорошо определена. А поскольку p[1234] означает *(p+1234), он тоже определен корректно.

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


Так как std::array упоминается в комментариях:

Если у нас есть std::array<std::array<int, 80>, 25> a;, то не существует a std::array<int, 2000>. Существует int[2000]. Я ищу все, что требует sizeof (std::array<T,N>) == sizeof (T[N])== N * sizeof (T)). Если этого нет, вы должны предположить, что могут быть пробелы, которые мешают обходу вложенных std::array.