Почему сглаживание многомерного массива в C незаконно?
Моя книга (Указатели на C Кеннетом Реком) гласит, что следующее незаконно, хотя оно отлично работает.
int arr[5][5];
int *p=&arr[2][2];
p=p+3; // As array is stored in row major form I think this
//should make p point to arr[3][0]
В книге говорится, что оставить одну строку в следующей строке незаконной. Но я не понимаю, почему.
Ответы
Ответ 1
Я надолго набросился на это, и я постараюсь изо всех сил объяснить, откуда я думаю, что он исходит, хотя и не читая книгу, это будет наилучшая гипотеза.
Во-первых, технически приращение, которое вы предлагаете (или он предложил), не является незаконным; разыменование это. Стандарт позволяет вам направить указатель на один последний элемент последовательности массива, из которого он был получен для оценки, но не для разыменования. Измените его на p = p + 4
, и оба они являются незаконными.
В стороне, линейный след массива, который не выдерживает, ar[2]
имеет тип, а int[5]
. Если вы не верите в это, рассмотрите следующее, все из которых правильно напечатаны:
int ar[5][5];
int (*sub)[5] = ar+2; // sub points to 3rd row
int *col = *sub + 2; // col points to 3rd column of third row.
int *p = col + 3; // p points to 5th colum of third row.
Не относится ли это к ar[3][0]
. Вы превысили объявленную величину измерения, участвующую в указателе-математике. Результат не может быть разыменован на законных основаниях и был бы больше, чем 3-смещение, и он не мог бы быть даже юридически оценен.
Помните, что массив, адресуемый, ar[2]
; а не только ar
, а said-same объявлен как size = 5. То, что оно подкрепляется двумя другими массивами одного и того же типа, не имеет отношения к решению, которое в настоящее время выполняется. Я считаю, Кристоф ответ на вопрос, предложенный как дубликат, должен был быть выбран для прямого решения. В частности, ссылка на C99 §6.5.6, p8, которая, хотя и многословная, появляется ниже:
Когда выражение, которое имеет целочисленный тип, добавляется или вычитается из указателя результат имеет тип операнда указателя. Если операнд указателя указывает на элемент объекта массива, а массив достаточно велика, результат указывает на смещение элемента от оригинальный элемент такой, что разность индексов результирующие и исходные элементы массива равны целочисленному выражению. Другими словами, если выражение P указывает на i-й элемент массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) указывают соответственно на я + n-й и i-n-ых элементов массива, при условии, что они существуют. Более того, если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает один за последним элементом объекта массива, и если выражение Q указывает один за последним элементом массива объект, выражение (Q) -1 указывает на последний элемент массива объект. Если и операнд указателя, и результат указывают на элементы одного и того же объекта массива, или один за последним элементом массива объект, оценка не должна приводить к переполнению; в противном случае поведение undefined. Если результат указывает один за последним элементом объекта массива, он не должен использоваться как операнд унарного * оператор, который оценивается.
Извините за спам, но выделенные жирным шрифтом - это то, что, по моему мнению, имеет отношение к вашему вопросу. Обращаясь так же, как и вы, вы оставляете массив адресованным и, таким образом, ходите в UB. Короче говоря, он работает (обычно), но не является законным.
Ответ 2
Причина, по которой книга говорит, что она незаконна, заключается в том, что арифметика указателей гарантирована для работы только с указателями на элементы в одном массиве или один за другим.
arr
- это массив из 5 элементов, в котором каждый элемент представляет собой массив из 5 целых чисел. Таким образом, теоретически, если вы хотите иметь указатели на элементы массива в arr[i]
, вы можете сделать только арифметику указателя, которая дает указатели в диапазоне &arr[i][0..4]
или arr[i]+5
, сохраняя i
константу.
Например, представьте, что arr
был одномерным из 5 целых чисел. Тогда указатель p
может указывать только на каждый из &arr[0..4]
или arr+5
(один за концом). Это также происходит с многомерными массивами.
С помощью int arr[5][5];
вы можете выполнять арифметику указателей, так что всегда есть указатель, который находится в диапазоне &arr[i][0..4]
или arr[i]+5
, - это то, что говорят правила. Это просто путают, потому что это массивы внутри массивов, но правило такое же, что бы ни было. Концептуально arr[0]
и arr[1]
представляют собой разные массивы, и хотя вы знаете, что они смежны в памяти, незаконно выполнять арифметику указателя между элементами arr[0]
и arr[1]
. Помните, что концептуально каждый элемент в arr[i]
является другим массивом.
В вашем примере, однако, p+3
будет указывать один за конец arr[2][2]
, поэтому он выглядит так, как будто он действителен. Это плохой выбор примера, потому что он будет p
указывать точно на один конец, делая его по-прежнему действительным. Если бы автор выбрал p+4
, пример был бы правильным.
В любом случае, у меня никогда не было проблем с выравниванием многомерных массивов на C с использованием аналогичных методов.
Также см. этот вопрос, он получил другую полезную информацию: Одномерный доступ к многомерному массиву: четко определенный C?
Ответ 3
Да. Это незаконно в C. Фактически, делая это, вы кладете свой компилятор. p
указывает на элемент arr[2][2]
(и имеет указатель на тип int
), т.е. третий элемент третьей строки. Оператор p=p+3;
будет увеличивать указатель p
до arr[2][5]
, что эквивалентно arr[3][0]
.
Но это не удастся, если память выделяется как мощность 2
(2
n
) на некоторой архитектуре. Теперь в этом случае распределение памяти будет округлено до 2
n
т.е. В вашем случае каждая строка будет округлена до 64
байтов.
См. Тестовую программу, в которой выделенная память представляет собой 5 распределений из 10 целых чисел. На некоторых машинах распределение памяти составляет не более 16 байт, поэтому запрошенные 40 байтов округляются до 48 байт на выделение:
#include <stdio.h>
#include <stdlib.h>
extern void print_numbers(int *num_ptr, int n, int m);
extern void print_numbers2(int **nums, int n, int m);
int main(void)
{
int **nums;
int n = 5;
int m = 10;
int count = 0;
// Allocate rows
nums = (int **)malloc(n * sizeof(int *));
// Allocate columns for each row
for (int i = 0; i < n; i++)
{
nums[i] = (int *)malloc(m * sizeof(int));
printf("%2d: %p\n", i, (void *)nums[i]);
}
// Populate table
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
nums[i][j] = ++count;
// Print table
puts("print_numbers:");
print_numbers(&nums[0][0], n, m);
puts("print_numbers2:");
print_numbers2(nums, n, m);
return 0;
}
void print_numbers(int *nums_ptr, int n, int m)
{
int (*nums)[m] = (int (*)[m])nums_ptr;
for (int i = 0; i < n; i++)
{
printf("%2d: %p\n", i, (void *)nums[i]);
for (int j = 0; j < m; j++)
{
printf("%3d", nums[i][j]);
}
printf("\n");
}
}
void print_numbers2(int **nums, int n, int m)
{
for (int i = 0; i < n; i++)
{
printf("%2d: %p\n", i, (void *)nums[i]);
for (int j = 0; j < m; j++)
printf("%3d", nums[i][j]);
printf("\n");
}
}
Пример вывода в Mac OS X 10.8.5; GCC 4.8.1:
0: 0x7f83a0403a50
1: 0x7f83a0403a80
2: 0x7f83a0403ab0
3: 0x7f83a0403ae0
4: 0x7f83a0403b10
print_numbers:
0: 0x7f83a0403a50
1 2 3 4 5 6 7 8 9 10
1: 0x7f83a0403a78
0 0 11 12 13 14 15 16 17 18
2: 0x7f83a0403aa0
19 20 0 0 21 22 23 24 25 26
3: 0x7f83a0403ac8
27 28 29 30 0 0 31 32 33 34
4: 0x7f83a0403af0
35 36 37 38 39 40 0 0 41 42
print_numbers2:
0: 0x7f83a0403a50
1 2 3 4 5 6 7 8 9 10
1: 0x7f83a0403a80
11 12 13 14 15 16 17 18 19 20
2: 0x7f83a0403ab0
21 22 23 24 25 26 27 28 29 30
3: 0x7f83a0403ae0
31 32 33 34 35 36 37 38 39 40
4: 0x7f83a0403b10
41 42 43 44 45 46 47 48 49 50
Пример вывода на Win7; GCC 4.8.1:
![enter image description here]()