Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: указатель на указатель
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Метценгерштейн
Встретил в коде следующую конструкцию:

Код
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
Всё что со знаками вопроса - верно. Проще никак. Смысл указателя на указатель, что бы вернуть значение не копируя содержимое в буфер-аргумента.
Последний кусок кода напрочь ошибочный. "pp_tk_value = &m_device_tk;" скопирует указатель в аргумент. Но при выходе из функции oob_key не поменяется.
x893
есть передать сам указатель вы его изменить не сможете.
у них если if (m_tag_match) то указатель меняется.
Всё правильно сделано (согласно их логике).
Метценгерштейн
Хорошо, а в моем приведенном примере- в чем ошибка? Не могу указателю сразу адрес присвоить разве?
x893
Цитата(Метценгерштейн @ Jul 27 2018, 14:51) *
Хорошо, а в моем приведенном примере- в чем ошибка? Не могу указателю сразу адрес присвоить разве?

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

ble_advdata_tk_value_t* oob_key;

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

P.S. Может книжечку почитать ?
"Стань профи С за 24 часа"
Метценгерштейн
Не нашел в инете такой книжки.

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

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

pp_tk_value = &m_device_tk;
где именно в данной строчке происходит разыменование указателя?
Метценгерштейн
Разыменование происходит в оригинальном примере:
*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, 19:54) *
В моем случае- на то же. Понимаю, что где-то не прав, но не доходит почему.
pp_tk_value уйдет из области видимости и останется в записи oob_key. Все изменения будут внесены в oob_key .

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

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


*pp_tk_value = &m_device_tk;
в этом месте, т.к. работаю с указателями по памяти, а не по значению.
Kabdim
Цитата(Метценгерштейн @ Jul 27 2018, 15:54) *
Понимаю, что где-то не прав, но не доходит почему.

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

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

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

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

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

Если ble_advdata_tk_value_t * pp_tk_value , в этом коде вы просто испортили начало массива pp_tk_value каким-то левым для содержимого этого массива адресом.
DASM
Цитата(Kabdim @ Jul 27 2018, 17:48) *
Но делать так, повторюсь, не надо.
Почему?
Метценгерштейн
Код
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
Попробую внести ясности-понятности.
Код
// допустим, 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
2Метценгерштейн: Видимо я утерял нить рассуждений. rolleyes.gif
Цитата(DASM @ Jul 27 2018, 18:22) *
Почему?

Соглашения по стилю обычно это определяют. Что по ссылке передаются данные которые долго копировать, но не нужно менять, а по указателям можно делать что угодно и это хорошо будет видно в коде благодаря звездочке/стрелке.
Herz
Цитата(Kabdim @ Jul 27 2018, 18:02) *
В стеке аллокируется кадр под все аргументы

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

Выделяется (англ. allocate).
Но что там куда выделяется зависит еще все-таки от соглашения вызовов используемого компилятора.
Метценгерштейн
Arlleex, спасибо за подробное изложение. Прошлись по основам. Подвели все к общему знаменателю.
Могли бы в том же духе первый пост разложить? Именно оригинальный пример из кода.
x893
Для АРМ параметры передаются через регистры (пока хватает). Так что Ваши параметры после выхода просто пропадут. Проще посмотреть отладчиком, если лень книги читать.
Arlleex
Цитата(Метценгерштейн @ 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


Метценгерштейн
Спасибо. Упустил из вида момент, что
Код
ble_advdata_tk_value_t *oob_key;

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

мы просто передаем адрес (0х1000) этой переменной. Как обычно.
Метценгерштейн
Спасибо. Упустил из вида момент, что
Код
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, 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
Возможно (вроде этого не было), стоит ещё упомянуть о передаче аргумента в функцию по адресу?

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

....

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


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

Впрочем, для передачи сложных типов часто используется запись вроде func(const int &arg),
позволяющая не тащить при вызове в стек весь тип, а обойтись его адресом. А компилятор проследит, чтобы по ходу функции аргумент не был изменён.
DASM
Давайте давайте учтите плохому.
"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." https://google.github.io/styleguide/cppguid...tput_Parameters
И это абсолютно правильно, делать ф-цию, вызов которой семантически выглядит как принимающая параметр по значении, а на деле давать ей неконстантную ссылку - форменное свинство
Professor Chaos
Цитата(Метценгерштейн @ 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 © 2001-2024 Invision Power Services, Inc.