В C, зависит ли смысл A [i] [j] от того, как объявляется A?
Предположим, что у меня есть двумерный массив grid
, объявленный как double grid[5][5]
. Я понимаю, что справедливы следующие утверждения:
- когда объявляется
grid
, смежный блок памяти выделяется для 5 * 5 удвоений, не более того,
- когда к элементу массива обращается обозначение
grid[i][j]
, этот фрагмент кода фактически интерпретируется как *(grid+(i*5+j))
.
С другой стороны, я знаю, что я также могу сохранить ту же матрицу, что и массив указателей, написав что-то вроде:
double ** grid;
grid = (double**)malloc(sizeof(double*)*5);
for (i=0; i<5; i++)
grid[i] = (double*)malloc(sizeof(double)*5);
и у меня есть код, который делает это. Проблема в том, что он затем переходит к элементам grid
, как и раньше, с двойной надписью. Разве этот случай отличается? В этом случае grid[i][j]
преобразуется в *(*(grid+i)+j)
, т.е. В двойное разделение? Это единственный способ увидеть, как это происходит правильно.
(Этот вопрос, вероятно, связан с моим (отсутствием) понимания взаимосвязи между типами указателей и массивов в C...)
EDIT:
Хорошо, посмотрим, получилось ли это прямо:
-
grid[i][j]
всегда преобразуется в *(*(grid+i)+j)
;
- это выражение действительно вычисляется по-разному в двух случаях, поскольку, как утверждает Джим в своем ответе, арифметика указателя учитывает размер указанного типа; тем не менее, правильный элемент извлекается в обоих случаях;
- тогда и только тогда, когда "сетка" является двумерным массивом, это выражение дополнительно оптимизировано до
*( (double*)grid + (i*5+j) )
, что возможно, потому что компилятор знает, что любой grid[i]
фактически является массивом, начинающимся с местоположения grid+i*5
.
Но это оставляет мне неизбежный вывод: для 2D-массива, если я устанавливаю i=j=0
, то у меня есть это **grid == *((double*)grid)
. Правильно ли это?
Ответы
Ответ 1
когда элемент массива обращается к сетке обозначений [i] [j], этот фрагмент кода фактически интерпретируется как *(grid+(i*5+j))
.
Нет. (Не знаю, почему так много ответов ответили "да" ). *(grid+(i*5+j))
- это то же самое, что и grid[i*5+j]
, что является ограниченным доступом для некоторых значений i
и j
. Кроме того, это указывает массив, а не int.
Следующие два выражения точно эквивалентны во всех случаях: A[i][j]
*(*(grid+i)+j)
.
Вы никогда "ничего не добиваетесь", перейдя между двумя различными обозначениями разыменования. Это всего лишь две эквивалентные формы синтаксиса выражения lvalue, которое обозначает конкретный объект.
Для остальной части моего ответа я буду использовать синтаксис [ ]
, поскольку я нахожу его более ясным.
Возможно, вы хотели спросить что-то вроде "с int A[5][5];
, тогда A[i][j]
эквивалентно смещению i+5*j
с начала A
?"
Другие ответы немного путаются, потому что термин "смещение по N" неоднозначен. N байтов или N int или N массивов int?
Если вы представляете себе, что A
были 1-мерным массивом длиной 25 (пусть называют это B
), то A[i][j]
обозначает тот же объект, что и B[i*5+j]
.
Чтобы выразить это в коде, вы должны написать: int *B = (int *)&A
. Это псевдонимы 2-мерного массива int как 1-D массива.
NB. Было бы неправильно писать int *B = (int *)A
, потому что A
распадается на &A[0]
, который имеет только пять ints, поэтому B[6]
по-прежнему является внешним доступом (поведение undefined). Если в вашем компиляторе вы не включили проверку границ, вероятно, вы ничего не заметите.
Ответ 2
x[i][j]
всегда точно эквивалентен *(*(x+i)+j)
... но вы должны иметь в виду, что арифметика указателя учитывает размер указанного типа. В
double ary[NROWS][NCOLS];
размер ary[i]
(т.е. *(ary+i)
) равен NCOLS*sizeof(double)
. В
double** ptr;
размер ptr[i]
(т.е. *(ptr+i)
) равен sizeof(double*)
.
В обоих случаях ary[i][j]
или ptr[i][j]
выбирается правильный элемент.
Ответ 3
Да. Вы идете правильно, но декларация
double grid[5][5]
и
double **grid;
разные. Сначала объявляется 2D-массив из 25 элементов типа double
, а второй объявляет указатель на указатель на тип double
. Оба они разные. Обратите внимание, что массивы не являются указателями.
В первом случае выделение памяти находится в стеке, и оно непрерывное, и, следовательно, компилятор оптимизирует grid[i][j]
до *(*(grid + i) + j)
, который далее оптимизируется до *(*grid + (i*5 + j))
.
Во втором случае память выделяется в куче, а malloc
не создает непрерывную память. В этом случае компилятор оптимизирует grid[i][j]
до *(*(grid + i) + j)
, но не оптимизирует его до *(*grid + (i*5 + j))
.
Ответ 4
Да, адрес A[i][j]
относится к делает зависимым от того, как объявлено A.
Арифметика указателя, используемая для оценки array_variable[i][j]
, зависит от количества столбцов (во втором измерении): это потому, что все элементы для каждой строки (первое измерение) хранятся вместе, а количество столбцов влияет на то, данные для второго, третьего, ETC. строка сохраняется.
В этом URL-адресе указано, что если у вас есть двумерный массив, объявленный как:
int [NUMBER_OF_ROWS][NUMBER_OF_COLUMNS]
Тогда:
x = y[a][b]
эквивалентно:
x = *((int *)y + a * NUMBER_OF_COLUMNS + b);
URL:
Как использовать выражения указателя для доступа к элементам двумерного массива в C?
В этом URL-адресе указано, что элементы (2-мерный массив) хранятся в строчном порядке: E.G. все элементы строки 1 сохраняются первыми.
http://www.cse.msu.edu/~cse251/lecture11.pdf
Этот URL-адрес имеет аналогичный расчет, в зависимости от размера второго измерения:
http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/pointer.html
addr( & arr[ row ][ col ] ) = addr( arr ) + [ sizeof( int ) * COLS * row ]
+ [ sizeof( int ) * col ]
Ответ 5
Дополнительно к другим ответам:
когда объявлена сетка, смежный блок памяти выделяется для 5 * 5 удвоений, не более того,
Это не совсем верно (часть с меньшим будет всегда верна), но иногда вы обнаружите, что, хотя вы получаете доступ к памяти после окончания массива, программа работает (не segfault моментально). Это означает, что иногда malloc может выделять больше памяти, чем вы просите. Конечно, это не означает, что вам нужно получить доступ к этой чрезмерной памяти.
Ответ 6
Q В C, зависит ли смысл A [i] [j] от того, как объявлен A?
A Нет, это не так.
Если у вас есть,
int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
тогда a[i]
оценивается как *(a+i)
.
Если у вас
int ** create2DArray(int d1, int d2)
{
int i = 0;
int ** a = malloc(sizeof(*a)*d1);
for ( ; i != d1; ++i )
{
a[i] = malloc(sizeof(int)*d2);
}
return a;
}
int **a = create2DArray(3, 3);
то a[i]
по-прежнему оценивается как *(a+i)
.
Независимо от того, как они объявлены, a[i][j]
оценивается как *(a[i] + j)
.
Вы все равно можете вызвать функцию, например:
void func(int* p, int size)
{
int i = 0;
for ( ; i != size; ++i )
{
printf("%d ", p[i]);
}
printf("\n");
}
используя
func(a[i]);
независимо от того, как был определен a
.
Основное различие между первым определением a
и вторым определением a
заключается в том, как выделяется память для него. В первом случае память выделяется в стеке, и она смежна. Во втором случае память выделяется в куче, и она, скорее всего, не соприкасается. Однако, как вы его используете и как компилятор относится к нему, он точно такой же.