a+1 = 0012FF74 a[0]+1 = 0012FF6C
Результат приводит к заключению, что единица в выражении a+1 «весит» 12 байт, так как в строке три элемента по четыре байта (не забывайте о том, что подсчеты надо производить в шестнадцатиричной системе исчисления), а единица в выражении a[0]+1 сдвигает нас от адреса первого элемента на четыре байта (один элемент типа int). Следующие две строки вывода — это единицы, что означает истинность равенств. Равенства: a==&a[0] и a[0]==&a[0][0] проходят без замечаний. Если же испытать другие два равенства: a==a[0] и a==&a[0][0], то они вызовут со стороны компилятора сообщения об ошибке. Интересно, что компилятор Borland C++ 4.5 в этом случае выводит лишь предупреждающее замечание (warning) о возможных проблемах при переносе кода. Результаты численного эксперимента могут быть описаны такой схемой:
a Ы &a[0] ~ &a[0][0]
*a Ы a[0] Ы &a[0][0]
**a Ы *a[0] Ы a[0][0]
Каждая следующая строчка в этой таблице получена из предыдущей путем применения операции разадресации (*). Здесь использованы следующие обозначения: Ы означает полную эквивалентность, в то время как ~ означает только численное равенство. Так и хочется для восстановления симметрии в правый верхний угол вместо &a[0][0] записать &&a[0][0], но такой объект не существует в языке С, хотя мы и говорим, что a имеет тип адрес адреса. Существует еще один объект (&a) из той же «обоймы», который не нашел своего места в таблице. Имеют место такие соотношения:
&a ~ a Ы &a[0] ~ &a[0][0]
Попробуйте самостоятельно прокомментировать эту цепочку равенств. Теперь посмотрим, как компилятор интерпретирует выражение a[i][j]. Сначала вычисляется a+i, что является адресом i+1 строки. Здесь по правилам адресной арифметики i — это число байт в i строках. Затем производится разадресация *(a+i) и добывается (еще только) адрес первого элемента i+1 строки (отсчет от единицы) . После этого вычисляется адрес элемента, сдвинутого на j единиц (уже других единиц): *(a+i)+j. Осталось разадресовать это выражение, чтобы достать элемент a[i][j]. Следовательно, a[i][j]==*(*(a+i)+j). Если задан массив int a[2][3];, то доступ к элементу a[1][1] может быть осуществлен с помощью выражения: *(*(a+1)+1). Согласно правилам адресной арифметики и в предположении, что sizeof(int)==4, единицы в этом выражении будут «весить» 12 и 4 байта. Предлагаем убедиться в справедливости сказанного, добавив в программу следующий вызов:
printf ("\n a[1][1]=%d *(*(a+1)+1)=%d\n\n a=%p a+1=%p *(a+1)+1=%p\n",
a[1][1],*(*(a+1)+1),a,a+1,*(a+1)+1);
Проанализируйте следующие модули и объясните что они выведут. Дополните текст необходимыми директивами препроцессора и отладьте в среде Visual Studio. Выполните в пошаговом режиме, объясняя результат выполнения каждой строки.
#define P puts("\n\n")
int a[]={0,1,2,3};
void main()
{
int i, *p;
for (P, p=&a[0]; p<=&a[3]; p++)
printf (" *p=%d",*p);
for (P, p=&a[0],i=1; i<4; i++)
printf (" p[%d]=%d",i,p[i]);
for (P, p=a,i=0; p+i<=a+3; p++,i++)
printf (" *(%p+%d)=%d",p,i,*(p+i));
for (P, p=a+3; p>=a; p--)
printf (" *p=%d",*p);
for (P, p=a+3; p>=a; p--)
printf (" a[%p-a]=%d",p,a[p-a]);
for (P, i=0; i<4; i++)
printf (" %d[a]=%d",i,i[a]);
P;
}
int a[]={0,1,2,3};
int *p[]={a,a+1,a+2,a+3}, **pp=p;
void main()
{
printf ("\n a[0]=%d a[1]= %d",a[0],a[1]);
printf ("\n **p= %d **(p+1)= %d",**p,**(p+1));
printf ("\n **pp=%d **(pp+1)=%d",**pp,**(pp+1));
printf ("\n a= %p a+1= %p",a,a+1);
printf ("\n *p= %p *(p+1)= %p",*p,*(p+1));
printf ("\n *pp=%p *(pp+1)=%p",*pp,*(pp+1));
printf ("\n p= %p p+1= %p",p,p+1);
printf ("\n pp= %p pp+1= %p",p,pp+1);
pp++;
printf ("\n pp= %p *pp = %p **pp=%d pp-p=%u", pp,*pp,**pp,pp-p);
printf ("\n *pp-a=%u",*pp-a);
printf ("\n p[1]-a=%u",p[1]-a);
printf ("\n **pp-*a=%d",**pp-*a);
(*pp)++;
printf ("\n pp=%p *pp=%p **pp=%d pp-p=%u", pp,*pp,**pp,pp-p);
printf ("\n *pp-a=%u",*pp-a);
printf ("\n **pp-*a=%d",**pp-*a);
++**pp;
printf ("\n pp=%p *pp=%p **pp=%d pp-p=%u", pp,*pp,**pp,pp-p);
puts("\n\n");
}
char *s[]={"Text","array","contains","five","elements"};
char **sp[]={s+4,s+3,s+2,s+1,s};
char ***spp=sp;
void main()
{
printf ("\n%s ",**++spp);
printf ("\n%s ",*--*++spp);
printf ("\n%s ",*++*--spp);
printf ("\n%s ",*(spp[1]+1));
printf ("\n%s ",*spp[3]);
printf ("\n%s ",*spp[-1]+3);
puts("\n\n");
}
Развивая дальше тему, рассмотрим способы динамического задания двухмерного массива. Предыдущие примеры, очевидно, убедили вас в преимуществах динамического выделения памяти для массивов переменных, в отличие от статического их задания. При решении на компьютере серьезных задач, например, краевых задач матфизики весьма важно экономно расходовать имеющуюся память и освобождать ее по мере возможности с тем, чтобы максимально повысить степень дискретизации области непрерывного изменения искомой величины и возможно точнее вычислить поле распределенной в пространстве функции. То же справедливо при разработке приложений, интенсивно использующих ресурсы графики. В них надо всегда иметь под рукой достаточное количество ресурсов GDI, которые лимитированы системой. Поэтому, эффективные алгоритмы и способы управления динамической памятью часто приобретают решающее значение. Принцип организации динамического двухмерного массива, который часто используется в подобных случаях, проще всего уяснить с помощью следующей схемы:
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.