Одномерный доступ к многомерному массиву: хорошо ли оно определено?

Я полагаю, что мы все согласны с тем, что для доступа к истинному многомерному массиву считается разыменоваемым (возможно смещенным) указателем на его первый элемент одномерным способом, например:

void clearBottomRightElement(int *array, int M, int N)
{
    array[M*N-1] = 0;  // Pretend the array is one-dimensional
}


int mtx[5][3];
...
clearBottomRightElement(&mtx[0][0], 5, 3);

Тем не менее, юрист-юрист во мне нуждается в убеждении, что это на самом деле хорошо определен C! В частности:

  • Является ли стандарт гарантией того, что компилятор не будет размещать промежутки между ними, например. mtx[0][2] и mtx[1][0]?

  • Обычно индексирование с конца массива (кроме одного конца конца) - это undefined (C99, 6.5.6/8). Итак, очевидно, что undefined:

    struct {
        int row[3];           // The object in question is an int[3]
        int other[10];
    } foo;
    int *p = &foo.row[7];     // ERROR: A crude attempt to get &foo.other[4];
    

    Таким образом, по тому же правилу можно ожидать, что следующее: undefined:

    int mtx[5][3];
    int (*row)[3] = &mtx[0];  // The object in question is still an int[3]
    int *p = &(*row)[7];      // Why is this any better?
    

    Так почему это нужно определить?

    int mtx[5][3];
    int *p = &(&mtx[0][0])[7];
    

Итак, какая часть стандарта C явно разрешает это? (Предположим ради обсуждения.)

ИЗМЕНИТЬ

Обратите внимание, что я не сомневаюсь, что это отлично работает во всех компиляторах. Я запрашиваю, разрешено ли это стандартным стандартом.

Ответы

Ответ 1

Единственное препятствие на пути доступа, которое вы хотите сделать, это то, что объекты типа int [5][3] и int [15] не допускаются к псевдониму друг другу. Таким образом, если компилятор знает, что указатель типа int * указывает на один из массивов int [3] первого, он может налагать ограничения границ массива, которые препятствовали бы доступу к чему-либо вне этого массива int [3].

Возможно, вам удастся обойти эту проблему, поместив все внутри объединения, содержащее как массив int [5][3], так и массив int [15], но я действительно не понимаю, использует ли профсоюз для использования в качестве типа на самом деле четко определены. Этот случай может быть немного менее проблематичным, так как вы не будете набирать типы отдельных ячеек, только логику массива, но я все еще не уверен.

Следует отметить особый случай: если ваш тип был unsigned char (или любым типом char), доступ к многомерному массиву как одномерному массиву был бы идеально определен. Это связано с тем, что одномерный массив unsigned char, который перекрывает его, явно определен стандартом как "представление" объекта, и по своей сути ему разрешено псевдонизировать его.

Ответ 2

Все массивы (включая многомерные) без дополнений. Даже если это явно не упоминается, это можно сделать из правил sizeof.

Теперь подписка на массив является частным случаем арифметики указателя, а в разделе C99 6.5.6, §8 четко указано, что поведение определяется только в том случае, если операнд указателя и результирующий указатель лежат в одном и том же массиве (или в одном из элементов), что делает возможным проверку границ языка C.

Это означает, что ваш пример - это поведение undefined. Однако, поскольку большинство реализаций C не проверяют границы, он будет работать как ожидалось - большинство компиляторов трактуют выражения undefined, такие как

mtx[0] + 5 

идентично хорошо определенным аналогам, например

(int *)((char *)mtx + 5 * sizeof (int))

который хорошо определен, потому что любой объект (включая весь двумерный массив) всегда можно рассматривать как одномерный массив типа char.


Дальнейшее размышление над формулировкой раздела 6.5.6, разделение внеочередного доступа на кажущееся четко выраженное подвыражение вроде

(mtx[0] + 3) + 2

рассуждение о том, что mtx[0] + 3 является указателем на один элемент за конец mtx[0] (делая первое дополнение хорошо определенным), а также указатель на первый элемент mtx[1] (делая вторую добавку -defined) неверно:

Даже если mtx[0] + 3 и mtx[1] + 0 гарантированно сравниваются с равными (см. раздел 6.5.9, §6), они семантически различны. Например, первое не может быть разыменовано и, следовательно, не указывает на элемент mtx[1].

Ответ 3

  • Уверен, что между элементами массива нет прокладки.

  • Существуют условия для вычисления адресов меньшего размера, чем полное адресное пространство. Это можно использовать, например, в огромном режиме 8086, так что часть сегмента не всегда будет обновляться, если компилятор знал, что вы не можете пересечь границу сегмента. (Это слишком давно для меня, чтобы напомнить, использовали ли компиляторы, которые я использовал, или нет).

С моей внутренней моделью - я не уверен, что она совершенно такая же, как и стандартная, и слишком сложно проверить, информация распространяется повсюду -

  • то, что вы делаете в clearBottomRightElement, действительно.

  • int *p = &foo.row[7]; undefined

  • int i = mtx[0][5]; undefined

  • int *p = &row[7]; не компилируется (gcc согласен со мной)

  • int *p = &(&mtx[0][0])[7]; находится в серой зоне (последний раз, когда я подробно проверил что-то вроде этого, я закончил рассмотрение недопустимого C90 и допустимого C99, это может быть здесь, или я мог что-то пропустить).

Ответ 4

Мое понимание стандарта C99 заключается в том, что нет необходимости, чтобы многомерные массивы были выложены в непрерывном порядке в памяти. Следуя единственной релевантной информации, которую я нашел в стандарте (каждый размер гарантированно будет смежным).

Если вы хотите использовать доступ x [COLS * r + c], я предлагаю вам придерживаться одномерных массивов.

Подстрока массива

Последовательные операторы индексов обозначают элемент многомерного объекта массива. Если E - n-мерный массив (n ≥ 2) с размерами я × j ×., × k, то E (используется как кроме lvalue) преобразуется в указатель на (n - 1) -мерный массив с размеры j ×., × k. Если унарный * оператор применяется к этому указателю явно или неявно в результате подписи, результатом является направленный (n - 1) -мерный массив, который сам преобразуется в указатель, если используется как значение, отличное от lvalue. Из этого следует что массивы хранятся в строчном порядке (последний индекс изменяется быстрее).

Тип массива

- Тип массива описывает смежно выделенный непустой набор объектов с конкретный тип объекта-члена, называемый типом элемента. 36) Типы массивов характеризуется их типом элемента и количеством элементов в массиве. тип массива называется производным от его типа элемента, и если его тип элемента равен T, Тип массива иногда называют массивом T. Построение типа массива из тип элемента называется "выводом типа массива".