Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум разработчиков электроники ELECTRONIX.ru _ Программирование _ указатель на указатель

Автор: Метценгерштейн Jul 27 2018, 10:48

Встретил в коде следующую конструкцию:

Код
ble_advdata_tk_value_t* oob_key;
err_code = nfc_tk_value_get(&oob_key);


где:

Код
typedef struct
{
  uint8_t tk[BLE_GAP_SEC_KEY_LEN];      /**< Array containing TK value in little-endian format. */
} ble_advdata_tk_value_t;


Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t ** pp_tk_value)
{
    if (m_tag_match)
    {
        *pp_tk_value = &m_device_tk;
        return NRF_SUCCESS;
    }
    else
    {
        return NRF_ERROR_NOT_FOUND;
    }
}


Есть вопрос. Как все проговорить что тут происходит?
Есть тип структуры ble_advdata_tk_value_t. Создали переменную- указатель на этот тип: oob_key.
Дальше? Передаем не просто адрес (сам указатель oob_key), а именно адрес этого указателя? А в ф-ии уже принимаем указатель на указатель?
Разыменовывали на один уровень вверх и по тому значению присвоили адрес уже массива.

А проще никак?
err_code = nfc_tk_value_get(oob_key);

и приняли:
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t * pp_tk_value)
{
    if (m_tag_match)
    {
        pp_tk_value = &m_device_tk;
        return NRF_SUCCESS;
    }
    else
    {
        return NRF_ERROR_NOT_FOUND;
    }
}

Автор: Kabdim Jul 27 2018, 11:05

Всё что со знаками вопроса - верно. Проще никак. Смысл указателя на указатель, что бы вернуть значение не копируя содержимое в буфер-аргумента.
Последний кусок кода напрочь ошибочный. "pp_tk_value = &m_device_tk;" скопирует указатель в аргумент. Но при выходе из функции oob_key не поменяется.

Автор: x893 Jul 27 2018, 11:44

есть передать сам указатель вы его изменить не сможете.
у них если if (m_tag_match) то указатель меняется.
Всё правильно сделано (согласно их логике).

Автор: Метценгерштейн Jul 27 2018, 11:51

Хорошо, а в моем приведенном примере- в чем ошибка? Не могу указателю сразу адрес присвоить разве?

Автор: x893 Jul 27 2018, 12:09

Цитата(Метценгерштейн @ Jul 27 2018, 14:51) *
Хорошо, а в моем приведенном примере- в чем ошибка? Не могу указателю сразу адрес присвоить разве?

Присвоить можете, только указатель

ble_advdata_tk_value_t* oob_key;

не изменится.

P.S. Может книжечку почитать ?
"Стань профи С за 24 часа"

Автор: Метценгерштейн Jul 27 2018, 12:25

Не нашел в инете такой книжки.

Не совсем понимаю, зачем мне менять указатель. Точнее, я же в их логике его и меняю через разыменование и записи по нему.

Автор: _pv Jul 27 2018, 12:39

на что будет указывать oob_key после выполнения функции в первом случае, и во втором?
ну или даже так: что произойдёт с переменной pp_tk_value после выхода из функции?

Цитата(Метценгерштейн @ Jul 27 2018, 19:25) *
Точнее, я же в их логике его и меняю через разыменование и записи по нему.

pp_tk_value = &m_device_tk;
где именно в данной строчке происходит разыменование указателя?

Автор: Метценгерштейн Jul 27 2018, 12:54

Разыменование происходит в оригинальном примере:
*pp_tk_value = &m_device_tk;

В моем случае, и в приведенном вами примере, конечно, разыменования не происходит. Просто идет присвоение адреса переменной типа указатель:
pp_tk_value = &m_device_tk;

Попробую ответить на первый вопрос.
После выполнения ф-ии oob_key будет указывать в первом случае на
&m_device_tk
т.к. происходит разыменование и присвоение адреса:
*pp_tk_value = &m_device_tk;

В моем случае- на то же. Понимаю, что где-то не прав, но не доходит почему.

pp_tk_value уйдет из области видимости и останется в записи oob_key. Все изменения будут внесены в oob_key .

Автор: _pv Jul 27 2018, 13:49

Цитата(Метценгерштейн @ Jul 27 2018, 19:54) *
В моем случае- на то же. Понимаю, что где-то не прав, но не доходит почему.
pp_tk_value уйдет из области видимости и останется в записи oob_key. Все изменения будут внесены в oob_key .

pp_tk_value это просто ещё один указатель, который указывает на то же на что и oob_key.
точнее указывал, а потом стал указывать на &m_device_tk.

в каком именно месте в вашем коде происходит запись в oob_key?

Автор: Метценгерштейн Jul 27 2018, 14:32

Цитата(_pv @ Jul 27 2018, 16:49) *
в каком именно месте в вашем коде происходит запись в oob_key?


*pp_tk_value = &m_device_tk;
в этом месте, т.к. работаю с указателями по памяти, а не по значению.

Автор: Kabdim Jul 27 2018, 14:48

Цитата(Метценгерштейн @ Jul 27 2018, 15:54) *
Понимаю, что где-то не прав, но не доходит почему.

Недостаток базовых знаний, вам совершенно не зря советуют книжки почитать. На уровне ассемблера при вызове функции все аргументы копируются в контекст функции. В самой функнции эти копии-аргументы можно менять как заблагорассудится, на источник своих значений они уже повлиять не могу. Поэтому и существуют указатели, что передать(скопировать) в функцию значение адреса объекта, который нужно изменить. Если бы вы программировали на с++, то вы могли бы(но делать так не стоит использовать ссылку для того что бы ваш пример заработал:
Цитата
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t *& pp_tk_value)

И тогда по ссылке изменения вернуться в oob_key. Но делать так, повторюсь, не надо. Нужно понять для чего нужны указатели и указатель на указатель.

Автор: Метценгерштейн Jul 27 2018, 14:50

Так я же не против почитать что-то) Читал много, но с этим вопросом не сталкивался. Если есть под рукой статья, где это все наглядно демонстрируется, то с удовольствием посмотрю.
Ну а в чем я в предыдущем посте не прав?

*pp_tk_value = &m_device_tk;
в этом месте, т.к. работаю с указателями по памяти, а не по значению.

Это тоже самое что вы и сказали:
На уровне ассемблера при вызове функции все аргументы копируются в контекст функции. В самой функнции эти копии-аргументы можно менять как заблагорассудится, на источник своих значений они уже повлиять не могу. Поэтому и существуют указатели, что передать(скопировать) в функцию значение адреса объекта, который нужно изменить.

Автор: Kabdim Jul 27 2018, 15:02

Упрощенно:
Переменная снаружи имеет какое-то значение
Выполняется вызов
В стеке аллокируется кадр под все аргументы
В эти новые места копируется значения переменных (связь между аргументами в функции и теми переменными что были использованы для вызова снаружи оборвалась)
В функции меняются аргументы как угодно, они находятся в том кадре что был сделан при вызове.
Возврат из функции (кадр аргументов освобождается, значения которые там находились становятся мусором)

Цитата(Метценгерштейн @ Jul 27 2018, 17:50) *
*pp_tk_value = &m_device_tk;

Если ble_advdata_tk_value_t * pp_tk_value , в этом коде вы просто испортили начало массива pp_tk_value каким-то левым для содержимого этого массива адресом.

Автор: DASM Jul 27 2018, 15:22

Цитата(Kabdim @ Jul 27 2018, 17:48) *
Но делать так, повторюсь, не надо.
Почему?

Автор: Метценгерштейн Jul 27 2018, 15:24

Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t ** pp_tk_value)
{
    if (m_tag_match)
    {
        *pp_tk_value = &m_device_tk;
        return NRF_SUCCESS;
    }
    else
    {
        return NRF_ERROR_NOT_FOUND;
    }
}

Это код из оригинального примера.
Произошло разыменование указателя и записался адрес чего- то. Но, если бы на вход пришел адрес массива, то да. А посмотрите внимательно- пришел адрес адреса))

Упрощенно:
Переменная снаружи имеет какое-то значение
Выполняется вызов
В стеке аллокируется кадр под все аргументы
В эти новые места копируется значения переменных (связь между аргументами в функции и теми переменными что были использованы для вызова снаружи оборвалась)
В функции меняются аргументы как угодно, они находятся в том кадре что был сделан при вызове.
Возврат из функции (кадр аргументов освобождается, значения которые там находились становятся мусором)


Полностью согласен. Эти вещи я знаю отлично. Это не тот вопрос, который мне не понятен. Для того и передают в ф-ю адрес переменной. Изменив ее там, она поменяется и там откуда ее переслали.

Автор: Arlleex Jul 27 2018, 15:41

Попробую внести ясности-понятности.

Код
// допустим, Data разместилась по адресу 0x5000
// значение по адресу 0x5000 после присвоения будет равно 10
int Data = 10;

// допустим, pData разместилась по адресу 0x10000
// значение по адресу 0x10000 после присвоения будет равно 0x5000 (адрес Data)
int *pData = &Data;

// чтобы работать на чтение/запись с содержимым Data через pData, используем операцию разыменовывания
int a = *pData; // a == 10
*pData = 20;   // теперь Data == 20

// допустим, ppData разместилась по адресу 0x20000
// значение по адресу 0x20000 после присвоения будет равно 0x10000 (адрес pData)
int **ppData = &pData;

// чтобы работать на чтение/запись с содержимым pData через ppData, используем операцию разыменовывания
int *a = *ppData; // a == 0x10000
// чтобы получить доступ к переменной Data, используется двойное разыменовывание
int b = **ppData; // b == 20

У меня, например, двойной уровень косвенного обращения (указатель на указатель) используется в кольцевом буфере.
Имеется автомат, который расшифровывает принимаемые символы от фреймера (байт-стаффинг) по UART и складывает их в кольцевой буфер. Когда фрейм данных полностью принят, нужно как-то сообщить пользователю о размере принятого сообщения. Поэтому, перед тем, как я складирую принимаемые символы в кольцевой буфер, я резервирую в нем один байт для записи в него размера посылки после того, как весь фрейм будет принят. Чтобы не городить лишних телодвижений, я использую что-то наподобие вот такой конструкции:
Код
char *pMessageSize; // адрес элемента в кольцевом буфере, содержащий информацию о размере принятого сообщения

...

RingBufferReserveByte(&pMessageSize); // прототип RingBufferReserveByte(char **Address)

...

// после приема всей посылки прямой записью в память добавил в заранее зарезервированное место в кольцевом буфере информацию о размере сообщения, не думая по положениях head- и tail-указателей буфера
*pMessageSize = ByteCounter;

// ну а тут уже можно выдать семафор в основную программу, где она считывает первый элемент - видит размер сообщения - считывает его, перемещает позицию следующего чтения и, (если там еще что-то уже успело придти) двигается так дальше

Автор: Kabdim Jul 27 2018, 15:42

2Метценгерштейн: Видимо я утерял нить рассуждений. rolleyes.gif

Цитата(DASM @ Jul 27 2018, 18:22) *
Почему?

Соглашения по стилю обычно это определяют. Что по ссылке передаются данные которые долго копировать, но не нужно менять, а по указателям можно делать что угодно и это хорошо будет видно в коде благодаря звездочке/стрелке.

Автор: Herz Jul 27 2018, 17:32

Цитата(Kabdim @ Jul 27 2018, 18:02) *
В стеке аллокируется кадр под все аргументы

Простите, что делается? 05.gif

Автор: Arlleex Jul 27 2018, 18:21

Цитата(Herz @ Jul 27 2018, 20:32) *
Простите, что делается? 05.gif

Выделяется (англ. allocate).
Но что там куда выделяется зависит еще все-таки от соглашения вызовов используемого компилятора.

Автор: Метценгерштейн Jul 27 2018, 19:42

Arlleex, спасибо за подробное изложение. Прошлись по основам. Подвели все к общему знаменателю.
Могли бы в том же духе первый пост разложить? Именно оригинальный пример из кода.

Автор: x893 Jul 27 2018, 19:46

Для АРМ параметры передаются через регистры (пока хватает). Так что Ваши параметры после выхода просто пропадут. Проще посмотреть отладчиком, если лень книги читать.

Автор: Arlleex Jul 28 2018, 11:15

Цитата(Метценгерштейн @ Jul 27 2018, 22:42) *
Могли бы в том же духе первый пост разложить? Именно оригинальный пример из кода.

Ну, попробую.
Код
// определение типа данных ble_advdata_tk_value_t
typedef struct
{
  uint8_t tk[BLE_GAP_SEC_KEY_LEN];
}ble_advdata_tk_value_t;


Код
// oob_key - указатель на любой объект в памяти, имеющий тип ble_advdata_tk_value_t
ble_advdata_tk_value_t *oob_key;

Допустим, для общего случая, что oob_key разместился по адресу 0x1000 и был объявлен в функции, поэтому без явной инициализации в нем будет лежать мусор. Значит по адресу 0x1000 сейчас лежит мусор.

В вызове
Код
err_code = nfc_tk_value_get(oob_key); // прототип ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t *pp_tk_value)

мы передаем копию значения oob_key (на данный момент мусор) в функцию nfc_tk_value_get(), а это значит, что, во-первых, в функцию передался мусор, а во-вторых, функция не сможет изменить значение по адресу 0x1000 (значение oob_key), поскольку она не знает адрес oob_key.
Поэтому повышается уровень косвенного обращения вызовом
Код
err_code = nfc_tk_value_get(&oob_key); // прототип ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t **pp_tk_value)

Здесь уже функции передается число 0x1000, и эта функция может что угодно делать с этим числом (это адрес oob_key) - например, инициализировать его адресом динамически созданного объекта типа ble_advdata_tk_value_t. В Вашем случае
Код
*pp_tk_value = &m_device_tk; // записать по адресу 0x1000 (то есть в ячейку oob_key) адрес объекта m_device_tk типа ble_advdata_tk_value_t



Автор: Метценгерштейн Jul 28 2018, 16:59

Спасибо. Упустил из вида момент, что

Код
ble_advdata_tk_value_t *oob_key;

это просто объявление переменной, но она непроинициализирована. А здесь
Код
err_code = nfc_tk_value_get(&oob_key);

мы просто передаем адрес (0х1000) этой переменной. Как обычно.

Автор: Метценгерштейн Jul 29 2018, 05:19

Спасибо. Упустил из вида момент, что

Код
ble_advdata_tk_value_t *oob_key;

это просто объявление переменной, но она непроинициализирована. А здесь
Код
err_code = nfc_tk_value_get(&oob_key);

мы просто передаем адрес (0х1000) этой переменной. Как обычно.

а вот здесь зачем указатель на указатель?
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t ** pp_tk_value)

Разве не достаточно было так:
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t * pp_tk_value)
{
    pp_tk_value = &m_device_tk;
}


Давайте аналогию приведем.

int a;
int *p;
p = &a;

Автор: andrew_b Jul 29 2018, 07:27

Цитата(Метценгерштейн @ Jul 29 2018, 08:19) *
Давайте аналогию приведем.

int a;
int *p;
p = &a;

Давйте.
Код
int a;
int *p;
p = &a;
Это "снаружи" функции.
В самой функции параметр (например, p1) является копией p. Изменяя p1, вы не можете изменить p.
Код
   /* При вызове функции происходит следующее */
   int *p1 = p; /* в параметр копируется указатель, передаваемый в функцию */
  
   int b; /* локальная переменная функции */
   p1 = &b; /* изменяется p1, т.е копия p, но сам p не изменяется */

Автор: GeorgK Aug 31 2018, 17:37

Возможно (вроде этого не было), стоит ещё упомянуть о передаче аргумента в функцию по адресу?

Код
void func(int &k) { k = 5};

....

int k = 0;
func(k);
//Здесь k равно 5.


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

Впрочем, для передачи сложных типов часто используется запись вроде func(const int &arg),
позволяющая не тащить при вызове в стек весь тип, а обойтись его адресом. А компилятор проследит, чтобы по ходу функции аргумент не был изменён.

Автор: DASM Sep 1 2018, 04:17

Давайте давайте учтите плохому.
"Within function parameter lists all references must be const:

void Foo(const string &in, string *out);
In fact it is a very strong convention in Googlecode that input arguments are values or const references while output arguments
are pointers." http://electronix.ru/redirect.php?https://google.github.io/styleguide/cppguide.html#Output_Parameters
И это абсолютно правильно, делать ф-цию, вызов которой семантически выглядит как принимающая параметр по значении, а на деле давать ей неконстантную ссылку - форменное свинство

Автор: Professor Chaos Sep 1 2018, 13:53

Цитата(Метценгерштейн @ Jul 29 2018, 08:19) *
а вот здесь зачем указатель на указатель?
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t ** pp_tk_value)

Разве не достаточно было так:
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t * pp_tk_value)
{
    pp_tk_value = &m_device_tk;
}

Вам надо понять (один раз и на всю жизнь), для чего в качестве аргумента функции передавать указатель на указатель.
Поймёте это - поймёте и ваш код.

То, что вы знаете, и о чём пишется в любом учебнике по С
1.Передавая объекты в функцию по-значению, вы сможете использовать их (значения), но не сможете их изменить. Все изменения аргументов внутри функции изменят лишь копии объектов, переданных в функцию, но не сами объекты. Т.е. изменение копии никак не отражается на оригинале, с которого копия была снята.
2. Чтобы можно было изменять значения объекта изнутри функции, в функцию в качестве аргумента надо передать не сам объект, а указатель на него. В этом случае в функцию передастся копия указателя на объект. Но т.к. копия указателя на объект указывает на тот же самый объект, что и исходный указатель, то этом случае функция через косвенную адресацию будет иметь доступ к такому объекту. Ведь в качестве параметра ей придёт указатель (т.е. фактически адрес) объекта. Зная адрес объекта (т.е. имея копию указателя на него) всегда можно изменить и сам объект, разыменовав указатель и присвоив ему новое значение.

А теперь то, что из этого следует, но на чём не акцентируется внимание в учебниках или вообще не говорится.
3. А что делать, если вам надо внутри функции изменить значение объекта имеющего тип указателя? Т.е. надо внутри функции присвоить указателю адрес другого объекта. Как такое сделать? Если передать в функцию сам указатель, то можно будет изменять тот объект, на который он указывает, но не сам указатель. А нам то этого не надо. Нам надо, чтобы указатель после выхода из функции указывал на другой объект (содержал его адрес). Вот для этого и приходится передавать в качестве аргументов функциям указатель на указатель.

Очень простой пример для иллюстрации сказанного в п. 3
Код
// Произвольный тип данных пользователя (предположим, int)
typedef int usertype_t;

// Функция, изменяющая значение указателя на объект типа usertype_t
// PtrToPtrToObj -указатель на указатель на объект типа usertype_t
// PtrToNewObj - указатель на новый объект
void SetPtrToNewObj (usertype_t** PtrToPtrToObj, usertype_t* PtrToNewObj) {
  *PtrToPtrToObj=PtrToNewObj;
}

int main (){    
    // Объекты типа usertype_t
  usertype_t ObjA, ObjB;
  
  // Указатель на объект типа usertype_t
  usertype_t* ObjPtr=&ObjA;       // Теперь ObjPtr указывает на объект ObjA
  
  // Меняем значение указателя. Нам нужно, чтобы он теперь указывал на другой объект
  // Эквивалент строки: ObjPtr=&ObjB;
  SetPtrToNewObj (&ObjPtr,&ObjB); // Теперь ObjPtr указывает на объект ObjB
}


А теперь зная это посмотрим на код:
Код
ret_code_t nfc_tk_value_get(ble_advdata_tk_value_t * pp_tk_value)
{
    pp_tk_value = &m_device_tk;
}

Что он делает?
pp_tk_value - это аргумент функции. Т.е. при вызове функции в него копируется значение типа ble_advdata_tk_value_t *. Т.е. это копия указателя на объект типа ble_advdata_tk_value_t. КОПИЯ указателя, а не сам указатель!
pp_tk_value = &m_device_tk; - что вы сделали этой строкой?
Вы присвоили КОПИИ УКАЗАТЕЛЯ новое значение. Но изменение КОПИИ никак не повлияло на оригинал. Оригинальный указатель остался прежним. А после выхода из функции pp_tk_value вышел из области видимости. Т.е. эта строка не изменила никаких объектов программы.
А должна была присвоить указателю новое значение - адрес объекта m_device_tk типа ble_advdata_tk_value_t.

Русская версия Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)