On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 14.06.18 16:55. Заголовок: Указатели: почему адрес массива совпадает с адресом его первого элемента?


Очень часто начинающие программисты не понимают, почему значение адреса всего массива совпадает с адресом его первого элемента. Например, почему выводимые значения данной демонстрационной программы равны друг другу.

 
#include <iostream>

int main()
{
int a[2];

std::cout << "&a = " << &a << ", a = " << a << '\n';
}


Вывод программы может выглядеть следующим образом:

 
&a = 0x7ffd8ee27c98, a = 0x7ffd8ee27c98


Как видно из вывода, оба значения адресов совпадают.

Для начала отметим, что в выражениях за редким исключением обозначение массива преобразуется в указатель на его первый элемент. Обратимся к стандарту C в виду его лаконичности по сравнению со стандартом C++, хотя сказанное верно также и для стандарта C++.

В разделе 6.3.2.1 Lvalues, arrays, and function designators написано


 цитата:
3 Except when it is the operand of the sizeof operator or the unary & operator, or is a
string literal used to initialize an array, an expression that has type ‘‘array of type’’ is
converted to an expression with type ‘‘pointer to type’’ that points to the initial element of
the array object and is not an lvalue. If the array object has register storage class, the
behavior is undefined.



Это означает, что в предложении

 
std::cout << "&a = " << &a << ", a = " << a << '\n';


из предыдущей демонстрационной программы выражение a (обозначение массива) можно заменить на &a[0], так как согласно приведенной цитате массив преобразуется в указатель на свой первый элемент.

Однако трудность для начинающих программистов представляет понимание того, что адрес самого массива, то есть значение выражения &a, равно значение выражения &a[0].

Чтобы проще было это понять, то давайте вместо массива рассмотрим структуру, например, такую

 
struct A
{
int x0;
int x1;
} a;


При виде данной структуры уже достаточно интуитивно понятно, что адрес объекта этой структуры, то есть выражение &a равно адресу его первого члена, объявленного в структуре, то есть выражению &x0. И, действительно, согласно разделу 6.7.2.1 Structure and union specifiers стандарта C:


 цитата:
15 Within a structure object, the non-bit-field members and the units in which bit-fields
reside have addresses that increase in the order in which they are declared. A pointer to a
structure object, suitably converted, points to its initial member (or if that member is a
bit-field, then to the unit in which it resides), and vice versa.
There may be unnamed
padding within a structure object, but not at its beginning.



Фактически, массив - это такая же структура, но состоящая из однородных элементов. Поэтому для массива, как и для структуры, адрес самого массива совпадает с адресом его первого элемента, так как область памяти, занимаемая всем массивом, начинается с области памяти, занимаемой его первым элементом.

Так что для этой демонстрационной программы

 
#include <iostream>

int main()
{
{
struct A
{
int x0;
int x1;
} a;

std::cout << "&a = " << &a << ", &a.x0 = " << &a.x0 << '\n';
}

{
int a[2];

std::cout << "&a = " << &a << ", &a[0] = " << &a[0] << '\n';
}
}


 
&a = 0x7ffc5d938308, &a.x0 = 0x7ffc5d938308
&a = 0x7ffc5d938300, &a[0] = 0x7ffc5d938300

оба адреса в каждой выводимой строке будут совпадать. Естественно выражение &a[0] можно было бы заменить на выражение a в предложении

 
std::cout << "&a = " << &a << ", a = " << a << '\n'


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


Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 2 [только новые]





ссылка на сообщение  Отправлено: 27.06.18 18:43. Заголовок: Еще один вопрос на S..


Еще один вопрос на Stackoverflow in multidimensional arrays in why does dereferencing address of a row again results inaddress instead of value, связанный с непониманием массивов и адресов.

Рассматривается такая программа

 
#include<stdio.h>
void main()
{
int a[4][3]={
{3,5,7},
{2,4,67},
{21,8,9},
{2,45,6}
};
printf("%u ",*(&a[0]+1));
}


Автор вопроса спрашивает, почему на консоль выводится значение адреса вместо предполагаемого им (автором вопроса) значения первого (нулевого) элемента во второй "строке" массива, то есть элемента a[1][0].

Для начала отметим, что согласно стандарту C функция main без параметров должна быть объявлена как

 
int main( void )


Предполагаемый выводимый элемент массива имеет тип int, поэтому следует использовать спецификатор преобразования %d вместо %u.

Теперь рассмотрим выражение
 
*(&a[0]+1)

Выражение a[0] - это одномерный массив типа int[3].
В свою очередь выражение &a[0] - это указатель на массив и имеет тип int ( * )[3].
Далее используется арифметика указателей &a[0] + 1. Этот указатель показывает на вторую "строку" массива. Фактически, это выражение эквивалентно выражению a + 1.

Если теперь применить разыменование к предыдущему выражению, *( &a[0] + 1 ), или, что тоже самое, *( a + 1 ), то получим массив типа int[3].

Пни использовании этого массива в качестве аргумента он неявно преобразуется в указатель на свой первый элемент, то есть функция printf попытается вывести указатель как целочисленное значение, что в общем случае ведет к неопределенному поведению.

Чтобы получить первый (нулевой) элемент второй "строки" массива, надо еще раз разыменовать полученный указатель. То есть программа будет выглядеть следующим образом

 
#include <stdio.h>

int main( void )
{
int a[4][3] =
{
{ 3, 5, 7 },
{ 2, 4, 67 },
{ 21, 8, 9 },
{ 2, 45, 6 }
};

printf( "%d ", **( &a[0] + 1 ) );
}


Чтобы было более понятно, то давайте рассмотрим переход от использования операторов индексации к указателям. Очевидно, что нужно вывести элемент a[1][0].

Выражение a[1] с помощью арифметики указателей эквивалентно может быть записано как *(a + 1 ).

В результате мы получаем эквивалентную запись ( *( a + 1 ) )[0].

Чтобы заменить второй оператор индексации на указатели, то следует записать *( *( a + 1 ) + 0 ).

Так как 0 в данном выражении не играет особой роли, то предыдущее выражение можно переписать как **( a + 1 ), или, что тоже самое, **( &a[0] + 1 ).

И, вообще, если у вас есть многомерный массив (допустим 3-мерный)

 
int a[N1][N2][N3];


то получить элемент массива с индексами i, j, k, то есть элемент a[ i ][ j ][ k ], используя арифметику указателей можно следующим образом

 
*( *( *( a + i ) + j ) + k )


Если какой-нибудь из индексов равен 0, например, индекс j, то можно убрать лишние скобки

 
*( *( *( a + i ) + 0 ) + k )

===>
 
*( **( a + i ) + k )


Ниже представлена демонстрационная программа

 
#include <stdio.h>

#define N1 2
#define N2 3
#define N3 4

int main( void )
{
int a[N1][N2][N3] =
{
{
{ 10, 20, 30, 40 },
{ 11, 21, 31, 41 },
{ 12, 22, 32, 42 },
},
{
{ 110, 120, 130, 140 },
{ 111, 121, 131, 141 },
{ 112, 122, 132, 142 },
}
};

size_t i = 1, j = 0, k = 2;

printf( "%d - %d\n", a[j][k], *( **( a + i ) + k ) );
}



Вывод программы на консоль:

 
130 - 130


Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 12.07.18 14:56. Заголовок: Работа с указателями..


Работа с указателями - одна из самых трудных тем для понимания для начинающих программистов.

Данное сообщение написано мною в ответ на следующий вопрос на Stackoverflow How to access elements of an array of function pointers using another pointer and in turn call one of the functions?

В вопросе спрашивается следующее.

Пусть имеется typedef объявление

 
typedef void (*ptr[4])( int a, int b)={fn1,fn2,fn3,fn4};


Как вызвать функции, указатели на которых используются в инициализации массива, имея следующий указатель

 
ptr *ptr_to_fn_arr;


Для начала заметим, что указанное typedef объявление является некорректным, так как в нем присутствуют инициализаторы. Правильно будет записать данное typedef объявление следующим образом

 
typedef void ( *ptr[4] )( int a, int b );


Тогда массив данного типа может быть объявлен в программе, например, следующим образом:

 
ptr functions = { fn1, fn2, fn3, fn4 };


Тогда объявление
 
ptr *ptr_to_fn_arr = &functions;

объявляет указатель на весь массив.

Так, как вызвать, например, функцию fn1, используя этот указатель?
Если разыменовать этот указатель

 
*ptr_to_fn_arr

то получим сам массив. В выражениях обозначение массива, как было уже сказано в предыдущих сообщениях данной темы, неявно преобразуется в указатель на свой первый элемент. То есть данное выражение, используемое как подвыражение другого выражения, будет иметь тип void ( ** )( int, int ). и будет указывать на первый элемент массива. Чтобы получить сам первый элемент массива, то есть указатель на исходную функцию fn1, нужно его снова разыменовать
 
**ptr_to_fn_arr

В этом случае выражение будет иметь тип void ( * )( int, int ).

Теперь, чтобы.вызвать указанную функцию, надо заключить выражение в круглые скобки и предоставить список аргументов. Необходимость круглых скобок объясняется тем, что операция разыменования имеет более низкий приоритет, чем операция вызова функции.
 
( **ptr_to_fn_arr )( a, b );

Вместо разыменования можно использовать оператор индексации. Тогда выражение может быть записано как
 
( *ptr_to_fn_arr )[0]( a, b );

Если требуется вызвать не первую функцию, а, например, вторую функцию из списка элементов массива, то можно использовать арифметику указателей.
 
( *( *ptr_to_fn_arr + 1 ) )( a, b );

или, опять-таки, оператор индексации
 
( *ptr_to_fn_arr )[1]( a, b );


Конечно проще было бы изначально объявлять не указатель на весь массив, а указатель на первый элемент массива. В этом случае мы избавимся от одного разыменования. Например,
 
void ( **ptr_to_fn_element )( int, int ) = functions;

В этом случае выражения вызова функций будет выглядеть проще. Например,
 
( ptr_to_fn_element )[1]( a, b );

и
 
( *( ptr_to_fn_element + 1 ) )( a, b );

Ниже представлена демонстрационная программа
 
#include <stdio.h>

void fn1( int a, int b )
{
long long int result = a + b;
printf( "%d + %d = %lld\n", a, b, result );
}

void fn2( int a, int b )
{
long long int result = a - b;
printf( "%d - %d = %lld\n", a, b, result );
}

void fn3( int a, int b )
{
long long int result = a * b;
printf( "%d * %d = %lld\n", a, b, result );
}

void fn4( int a, int b )
{
long long int result = a / b;
printf( "%d + %d = %lld\n", a, b, result );
}

typedef void ( *ptr[4] )( int a, int b );

int main( void )
{
ptr functions = { fn1, fn2, fn3, fn4 };
ptr *ptr_to_fn_arr = &functions;

int a = 20, b = 2;

( *ptr_to_fn_arr )[0]( a, b );
( **ptr_to_fn_arr )( a, b );

putchar( '\n' );

( *ptr_to_fn_arr )[1]( a, b );
( *( *ptr_to_fn_arr + 1 ) )( a, b );

void ( **ptr_to_fn_element )( int, int ) = functions;

putchar( '\n' );

( ptr_to_fn_element )[0]( a, b );
( *ptr_to_fn_element )( a, b );

putchar( '\n' );

( ptr_to_fn_element )[1]( a, b );
( *( ptr_to_fn_element + 1 ) )( a, b );
}

Ее вывод на консоль:
 
20 + 2 = 22
20 + 2 = 22

20 - 2 = 18
20 - 2 = 18

20 + 2 = 22
20 + 2 = 22

20 - 2 = 18
20 - 2 = 18


Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 10
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет