|
указатель на указатель, помочь разобраться |
|
|
|
Jul 27 2018, 10:48
|
Профессионал
    
Группа: Свой
Сообщений: 1 357
Регистрация: 12-04-05
Из: Петербург
Пользователь №: 4 079

|
Встретил в коде следующую конструкцию: Код 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; } }
|
|
|
|
2 страниц
1 2 >
|
 |
Ответов
(1 - 27)
|
Jul 27 2018, 12:09
|

Профессионал
    
Группа: Свой
Сообщений: 1 333
Регистрация: 27-10-08
Из: Планета Земля
Пользователь №: 41 226

|
Цитата(Метценгерштейн @ Jul 27 2018, 14:51)  Хорошо, а в моем приведенном примере- в чем ошибка? Не могу указателю сразу адрес присвоить разве? Присвоить можете, только указатель ble_advdata_tk_value_t* oob_key; не изменится. P.S. Может книжечку почитать ? "Стань профи С за 24 часа"
|
|
|
|
|
Jul 27 2018, 12:39
|
Гуру
     
Группа: Свой
Сообщений: 2 563
Регистрация: 8-04-05
Из: Nsk
Пользователь №: 3 954

|
на что будет указывать oob_key после выполнения функции в первом случае, и во втором? ну или даже так: что произойдёт с переменной pp_tk_value после выхода из функции? Цитата(Метценгерштейн @ Jul 27 2018, 19:25)  Точнее, я же в их логике его и меняю через разыменование и записи по нему. pp_tk_value = &m_device_tk; где именно в данной строчке происходит разыменование указателя?
|
|
|
|
|
Jul 27 2018, 14:48
|
Знающий
   
Группа: Свой
Сообщений: 558
Регистрация: 26-11-14
Из: Зеленоград
Пользователь №: 83 842

|
Цитата(Метценгерштейн @ 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, 15:02
|
Знающий
   
Группа: Свой
Сообщений: 558
Регистрация: 26-11-14
Из: Зеленоград
Пользователь №: 83 842

|
Упрощенно: Переменная снаружи имеет какое-то значение Выполняется вызов В стеке аллокируется кадр под все аргументы В эти новые места копируется значения переменных (связь между аргументами в функции и теми переменными что были использованы для вызова снаружи оборвалась) В функции меняются аргументы как угодно, они находятся в том кадре что был сделан при вызове. Возврат из функции (кадр аргументов освобождается, значения которые там находились становятся мусором) Цитата(Метценгерштейн @ Jul 27 2018, 17:50)  *pp_tk_value = &m_device_tk; Если ble_advdata_tk_value_t * pp_tk_value , в этом коде вы просто испортили начало массива pp_tk_value каким-то левым для содержимого этого массива адресом.
|
|
|
|
|
Jul 27 2018, 15:24
|
Профессионал
    
Группа: Свой
Сообщений: 1 357
Регистрация: 12-04-05
Из: Петербург
Пользователь №: 4 079

|
Код 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; } } Это код из оригинального примера. Произошло разыменование указателя и записался адрес чего- то. Но, если бы на вход пришел адрес массива, то да. А посмотрите внимательно- пришел адрес адреса)) Упрощенно: Переменная снаружи имеет какое-то значение Выполняется вызов В стеке аллокируется кадр под все аргументы В эти новые места копируется значения переменных (связь между аргументами в функции и теми переменными что были использованы для вызова снаружи оборвалась) В функции меняются аргументы как угодно, они находятся в том кадре что был сделан при вызове. Возврат из функции (кадр аргументов освобождается, значения которые там находились становятся мусором)Полностью согласен. Эти вещи я знаю отлично. Это не тот вопрос, который мне не понятен. Для того и передают в ф-ю адрес переменной. Изменив ее там, она поменяется и там откуда ее переслали.
|
|
|
|
|
Jul 27 2018, 15:41
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Попробую внести ясности-понятности. Код // допустим, 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;
// ну а тут уже можно выдать семафор в основную программу, где она считывает первый элемент - видит размер сообщения - считывает его, перемещает позицию следующего чтения и, (если там еще что-то уже успело придти) двигается так дальше
|
|
|
|
|
Jul 27 2018, 15:42
|
Знающий
   
Группа: Свой
Сообщений: 558
Регистрация: 26-11-14
Из: Зеленоград
Пользователь №: 83 842

|
2 Метценгерштейн: Видимо я утерял нить рассуждений. Цитата(DASM @ Jul 27 2018, 18:22)  Почему? Соглашения по стилю обычно это определяют. Что по ссылке передаются данные которые долго копировать, но не нужно менять, а по указателям можно делать что угодно и это хорошо будет видно в коде благодаря звездочке/стрелке.
|
|
|
|
|
Jul 28 2018, 11:15
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(Метценгерштейн @ 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
|
Профессионал
    
Группа: Свой
Сообщений: 1 357
Регистрация: 12-04-05
Из: Петербург
Пользователь №: 4 079

|
Спасибо. Упустил из вида момент, что Код ble_advdata_tk_value_t *oob_key; это просто объявление переменной, но она непроинициализирована. А здесь Код err_code = nfc_tk_value_get(&oob_key); мы просто передаем адрес (0х1000) этой переменной. Как обычно.
|
|
|
|
|
Jul 29 2018, 05:19
|
Профессионал
    
Группа: Свой
Сообщений: 1 357
Регистрация: 12-04-05
Из: Петербург
Пользователь №: 4 079

|
Спасибо. Упустил из вида момент, что Код 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;
|
|
|
|
|
Jul 29 2018, 07:27
|
Профессионал
    
Группа: Свой
Сообщений: 1 975
Регистрация: 30-12-04
Из: Воронеж
Пользователь №: 1 757

|
Цитата(Метценгерштейн @ 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 не изменяется */
|
|
|
|
|
Aug 31 2018, 17:37
|
Группа: Участник
Сообщений: 9
Регистрация: 23-08-18
Пользователь №: 107 025

|
Возможно (вроде этого не было), стоит ещё упомянуть о передаче аргумента в функцию по адресу? Код void func(int &k) { k = 5};
....
int k = 0; func(k); //Здесь k равно 5. Иногда это удобнее, если только не забывать, что изменение аргумента скажется на вызывающей функции. Впрочем, для передачи сложных типов часто используется запись вроде func(const int &arg), позволяющая не тащить при вызове в стек весь тип, а обойтись его адресом. А компилятор проследит, чтобы по ходу функции аргумент не был изменён.
|
|
|
|
|
Sep 1 2018, 04:17
|
Гуру
     
Группа: Свой
Сообщений: 3 644
Регистрация: 28-05-05
Пользователь №: 5 493

|
Давайте давайте учтите плохому. "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И это абсолютно правильно, делать ф-цию, вызов которой семантически выглядит как принимающая параметр по значении, а на деле давать ей неконстантную ссылку - форменное свинство
|
|
|
|
|
Sep 1 2018, 13:53
|

Участник

Группа: Участник
Сообщений: 60
Регистрация: 25-08-17
Пользователь №: 98 970

|
Цитата(Метценгерштейн @ 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.
Сообщение отредактировал Professor Chaos - Sep 1 2018, 15:08
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|