Зачем использовать двойное косвенное обращение?или зачем использовать указатели на указатели? - PullRequest
232 голосов
/ 07 апреля 2011

Когда следует использовать двойное косвенное в C? Кто-нибудь может объяснить на примере?

Что я знаю, так это то, что двойная косвенность является указателем на указатель. Зачем мне указатель на указатель?

Ответы [ 16 ]

2 голосов
/ 22 февраля 2017

Ниже приведен очень простой пример C ++, который показывает, что если вы хотите использовать функцию для установки указателя для указания на объект, вам нужен указатель на указатель .В противном случае, указатель будет продолжать возвращаться к нулю .

(ответ на C ++, но я считаю, что то же самое в C.)

(Также для справки: Google("передача по значению c ++") = "По умолчанию аргументы в C ++ передаются по значению. Когда аргумент передается по значению, значение аргумента копируется в параметр функции.")

Итак, мы хотимустановить указатель b равным строке a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

Что происходит в строке Function_1(&a, b);?

  • «Значение»&main::a (адрес) копируется в параметр std::string* Function_1::a.Следовательно, Function_1::a является указателем (то есть адресом памяти) строки main::a.

  • «Значение» main::b (адрес в памяти) копируется впараметр std::string* Function_1::b.Поэтому в памяти теперь есть 2 из этих адресов, оба - нулевые указатели.В строке b = a; локальная переменная Function_1::b затем изменяется на Function_1::a (= &main::a), но переменная main::b не изменяется.После вызова Function_1, main::b по-прежнему является нулевым указателем.

Что происходит на линии Function_2(&a, &b);?

  • Обработка переменной a такая же: внутри функции Function_2::a является адресом строки main::a.

  • Но переменная b сейчас используетсяпередается как указатель на указатель.«Значение» &main::b (адрес указателя main::b) копируется в std::string** Function_2::b.Следовательно, в Function_2 разыменование этого значения как *Function_2::b вызовет и изменит main::b.Таким образом, строка *b = a; фактически устанавливает main::b (адрес), равный Function_2::a (= адрес main::a), что мы и хотим.

Если вы хотите использовать функцию для изменения объекта, будь то объект или адрес (указатель), вы должны передать указатель на этот объект. То, что вы фактически передаетене может быть изменено (в области вызова), потому что сделана локальная копия.

(Исключением является то, что параметр является ссылкой, например, std::string& a. Но обычно это const. Обычно, еслиВы называете f(x), если x является объектом, вы должны предположить, что f не будет изменять x. Но если x - указатель, то вы должны предположить, чточто f может изменить объект, на который указывает x.)

2 голосов
/ 07 апреля 2011

Я постоянно их использую, когда у меня есть массив объектов, и мне нужно выполнить поиск (бинарный поиск) по ним по разным полям.
Я сохраняю исходный массив ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Затем создайте массив отсортированных указателей на объекты.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Вы можете создать столько отсортированных массивов указателей, сколько вам нужно, а затем использовать двоичный поиск в массиве отсортированных указателей, чтобы получить доступ к нужному объекту по имеющимся у вас данным. Исходный массив объектов может оставаться несортированным, но каждый массив указателей будет отсортирован по указанному полю.

2 голосов
/ 07 апреля 2011

Например, если вы хотите произвольный доступ к несмежным данным.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- в С

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Вы храните указатель p, который указывает на массив указателей. Каждый указатель указывает на часть данных.

Если sizeof(T) велико, может быть невозможно выделить непрерывный блок (т. Е. Используя malloc) из sizeof(T) * n байтов.

1 голос
/ 27 ноября 2016

Почему двойные указатели?

Цель состоит в том, чтобы изменить то, на что указывает студент А, с помощью функции.

#include <stdio.h>
#include <stdlib.h>


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */
0 голосов
/ 23 мая 2019

Сравнить изменение значение переменной против изменения значение указателя :

#include <stdio.h>
#include <stdlib.h>

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

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

OLD (плохо):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NEW (лучше):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
0 голосов
/ 11 ноября 2014

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

Вы ДОЛЖНЫ использовать двойные указатели, когда работаете с указателями, которые изменены в других местах вашего приложения. Вы также можете найти двойные указатели необходимыми, когда имеете дело с оборудованием, которое возвращает и обращается к вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...