Мы должны начать с проверки того, что на самом деле представляет собой int [5] [5]. Используемые типы:
- INT
- массив [5] целых
- массив [5] массивов
Не задействован массив [25] целых.
Правильно, что размер семантики подразумевает, что массив в целом является смежным. Массив [5] из ints должен иметь 5 * sizeof (int) и рекурсивно применяться, a [5] [5] должен иметь 5 * 5 * sizeof (int). Нет места для дополнительных прокладок.
Кроме того, массив в целом должен работать, когда он передается в memset, memmove или memcpy с sizeof. Также должна быть возможность перебора всего массива с помощью (char *). Итак, действительная итерация:
int a[5][5], i, *pi;
char *pc;
pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
pi = (int *)pc;
DoSomething(pi);
pc += sizeof(int);
}
Делать то же самое с (int *) было бы неопределенным поведением, потому что, как уже говорилось, нет задействованного массива [25] для int. Использование союза, как в ответе Кристофа, также должно быть правильным. Но есть еще один момент, усложняющий это далее, оператор равенства:
6.5.9.6
Два указателя сравниваются равными в том и только в том случае, если оба являются нулевыми указателями, оба являются указателями на один и тот же объект (включая указатель на объект и подобъект в его начале) или на функцию, оба являются указателями на один после последнего элемента того же массива object, или один - указатель на один за концом одного объекта массива, а другой - указатель на начало другого объекта массива, который непосредственно следует за первым объектом массива в адресном пространстве. 91)
91) Два объекта могут быть смежными в памяти, потому что они являются смежными элементами большого массива или смежными элементами структуры без заполнения между ними, или потому что реализация решила разместить их так, даже если они не связаны между собой. Если предыдущие недопустимые операции с указателями (например, доступ за пределы массива) вызывали неопределенное поведение, последующие сравнения также приводят к неопределенному поведению.
Это значит для этого:
int a[5][5], *i1, *i2;
i1 = &a[0][0] + 5;
i2 = &a[1][0];
i1 сравнивается как равный i2. Но при итерации по массиву с (int *) это все еще неопределенное поведение, потому что он изначально получен из первого подмассива. Он волшебным образом не конвертируется в указатель во второй подмассив.
Даже при этом
char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int *i3 = (int *)c;
не поможет. Он сравнивается равным i1 и i2, но он не получен ни от одного из подмассивов; в лучшем случае это указатель на один int или массив [1] из int.
Я не считаю это ошибкой в стандарте. И наоборот: разрешение этого приведет к особому случаю, который нарушает либо систему типов для массивов, либо правила для арифметики указателей, либо и то и другое. Это можно считать отсутствующим определением, но не ошибкой.
Таким образом, даже если макет памяти для [5] [5] идентичен макету для [25], и тот же цикл с использованием (char *) можно использовать для итерации обоих, реализация разрешено взрывать, если один используется в качестве другого. Я не знаю, почему он должен или знает какую-либо реализацию, которая могла бы, и, возможно, в Стандарте есть один факт, не упомянутый до сих пор, который делает его хорошо определенным поведением. До тех пор я бы посчитал это неопределенным и остался бы в безопасности.