правильное использование указателя на указатель - PullRequest
1 голос
/ 22 января 2009

У меня есть функция, которая принимает указатель на указатель на структуру.

struct ABC;

void func(ABC ** ptr);  //func is provided by someone else and i don't know about the implementation.
{


}

Теперь у меня есть следующий код.

ABC xyz[2];
ABC * ptr = xyz;

ABC **dptr1 = &ptr;     //pointer to ponter

ABC ** dptr2 = (ABC **)malloc(2*sizeof(struct abc*)); //pointer to arrary of pointers

dptr2[0] = &xyz[0];
dptr2[1] = &xyz[1];

если я передаю dptr1 в func, а func делает что-то вроде * ptr [0] и * ptr [1] для доступа к фактическим значениям, это не удается.

Итак, каков стандартный способ обхода двойного указателя.

Ответы [ 6 ]

4 голосов
/ 22 января 2009

Когда вы передаете ptr1 в func, вы фактически передаете указатель на память, занятую ptr. Проблема в том, что func, похоже, предполагает, что ptr1 фактически указывает начало массива указателей, но вы передали ему указатель на "массив" только из одного указателя, ptr. func вероятно нанесет большой урон вашему стеку.

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

В вашем коде я бы написал

ABC xyz[2];     // two ABC's
ABC * ptrs[2];  // array of pointers to ABC's
ptrs[0] = &xyz[0];
ptrs[1] = &xyz[1];
func(ptrs);    // C compiler convers an array of pointers to
               // a pointer to pointers when passing it to a function 

Кроме того, поскольку в C нет понятия параметров массива, обычно рекомендуется указывать дополнительный параметр с рассматриваемой длиной массива, либо вы можете оставить себя открытым для уязвимостей переполнения буфера. Ваша func функция могла бы быть написана func(ABC ** ppabc, int elements), чтобы лучше отражать, как она намеревается использовать свои параметры. Текущая сигнатура функции подразумевает, что в действительности func будет использовать только указатель, на который указывает указатель на указатель (попробуйте сказать это быстро три раза!)

3 голосов
/ 22 января 2009

Из интерфейса функции:

void func(ABC** ptr)
{
}

Это может быть одна из двух вещей.

  • Вы передаете указатель на указатель
  • Вы передаете указатель на двумерный массив.

Различия невелики.
Указатель на указатель указывает, что func () изменится (* ptr), чтобы указать на какой-либо объект. Это может быть выделение памяти или другая операция.

Если это двумерный массив (как вы тоже избегаете в своем вопросе)

ABC   xyz[2];
ABC*  ptr = xyz;

ABC** dptr1   = &ptr;     //pointer to ponter
// or 
ABC*  dptr1[] = {xyz};    //pointer to an array.
                          //Thus making dptr the 'logical' equivalent of ABC[1][2]


> if i pass dptr1 to func, and func does something like *ptr[0] and *ptr[1] to access the actual values, it fails.

Если переданное значение является двумерным массивом. Не путайте это с этим:

ABC  value[1][2];

Технически это также двумерный массив, но расположение памяти для двух форм совершенно разное. Оригинал действительно является массивом массивов ABC, в то время как это настоящий двумерный массив.

ABC   row1[3] = {/*STUFF 1*/};
ABC   row2[3] = {/*STUFF 2*/};
ABC   row3[3] = {/*STUFF 3*/};
ABC*  data[3] = {row1,row2,row3};  // logical ABC data[3][3];

func(data);
2 голосов
/ 22 января 2009

Массивы в Си - это в основном указатели. Единственная реальная разница в том, что компилятор знает, на сколько элементов он указывает.

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

Как уже упоминалось, важное различие между массивами и указателями является результатом оператора sizeof(): для указателей он возвращает количество байтов, необходимое для хранения указателя, для массивов он возвращает количество необходимых байтов хранить элементы!

Следовательно, можно определить количество элементов в массиве с помощью следующего макроса:

#define count(ARRAY) (sizeof(ARRAY)/sizeof(*ARRAY))

Вместо *ARRAY вы также можете использовать ARRAY[0].

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

void func(struct ABC * array, size_t elementCount) {...}

struct ABC anArray[2] = {...};
func(anArray, count(anArray));

Мы используем наш макрос count(), поэтому нам нужно изменить размер массива только в одном месте.


Итак, почему ваша функция ожидает двойной указатель? Скорее всего, чтобы элементы массива произвольно помещались в память. Поэтому, если вы сохранили свои элементы в простом старом массиве, вы должны преобразовать его:

struct ABC array[2] = {...};
struct ABC * parray[count(array)];

for(size_t i = 0; i < count(array); ++i)
    parray[i] = &array[i];

Кроме того, если func действительно не принимает второй аргумент для размера массива, это может означать, что он принимает только массивы определенного размера (тогда сигнатура функции должна была выглядеть примерно как func(struct ABC * array[5])) или что он ожидает, что определенный элемент будет обозначать конец массива (скорее всего, NULL). В этом случае вы должны сделать

struct ABC * parray[count(array) + 1];
parray[count(array)] = NULL;

Также еще одним решением будет использование двумерного массива:

struct ABC parray[][1] = {
     { {/* first struct's initializer */} },
     { {/* second struct's initializer */} },
     { {/* third struct's initializer */} }
};

func(parray, count(parray));
1 голос
/ 22 января 2009

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

0 голосов
/ 22 января 2009

Используйте определения типов для облегчения чтения кода:

typedef ABC* ABC_ptr;

Затем используйте malloc вместо new:

ABC_ptr *dptr2 = new ABC_ptr[2];

Вы должны иметь доступ к * ptr [0] внутри func. Вы уверены, что исходная таблица ABC инициализирована?

0 голосов
/ 22 января 2009

Ну, вы пометили это как C ++, так что в этом примере я собираюсь перейти на новый вместо malloc. Malloc - немного простой C. Если вы действительно хотите правильно использовать C ++, я бы предложил вместо этого использовать std :: vector.

struct ABC
{
  // some members;
};


void func( ABC* firstElemOfArray, int countOfElementsInArray)
{
  for( int i = 0; i < countOfElementsInArray; ++i )
  {
     ABC* currentElement = firstElemOfArray + i;
     // do something with current elements
  }
}

void useFunc()
{
   ABC array[2] = { new ABC(), new ABC() };

   func( array, sizeof(array/sizeof(array[0]) );
}

По сути, вам не нужно делать что-то слишком сложное, просто передайте указатель на первый элемент массива с длиной массива.

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