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

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

void list_insert(struct list *h, int d)
{
        struct list *elem = malloc(sizeof(struct list));

        elem->data = d;
        elem->next = NULL;

        if (!h) {
                h = elem;
        } else {
                elem->next = h;
                h = elem;
        }
}           

Я заметил, что эта функция, кажется, не изменяет внешний вид переменной h (то есть все, что передается в list_insert), и я заметил, что печать в конце функции вставки, кажется, работает. Поэтому, попытавшись найти ответы в Интернете, я не смог найти ничего очевидного, но я обнаружил, что большинство реализаций списков будут иметь двойные указатели. Я изменил свою функцию, чтобы использовать двойные указатели, и вдруг она заработала. Может ли кто-нибудь помочь мне понять, что здесь происходит, поскольку я знаю, что управление указателями является важной концепцией, и я думаю, что понимаю, что такое указатель и как он связан с памятью, но я не думаю, что понимаю, почему один указатель не изменяется, тогда как двойной указатель делает.

Спасибо!


person user3043746    schedule 29.11.2013    source источник
comment
когда вы передаете параметр функции, параметр копируется, и функция работает с копией оригинала. Когда вы передаете указатель (указатель=int копируется) и выполняете *pointer=something, вы изменяете данные, на которые указывает указатель, что позволяет вам видеть изменения вне функции. таким образом, var * * будет указателем с возможностью записи.   -  person Exceptyon    schedule 29.11.2013


Ответы (3)


В C аргументы функции передаются значениями. Даже указатели передаются значениями.

Например:

#include<malloc.h>
#include<stdio.h>
int allocatingMemory(int* ptr)
{
    ptr = malloc(sizeof(int)); 
    if(ptr==NULL)
        return -1;
    else 
        return 0;
}// We are not returning the pointer to allocated memory

int main(void)
{
    int* ptr;
    int allocated = allocatingMemory(ptr);
    if(allocated == 0)
    {
        *ptr = 999;// Boom!!! 
        free(ptr);
    }

    return 0;
}

Чтобы решить эту проблему, мы используем

int allocatingMemory(int** ptr)
{
    *ptr = malloc(sizeof(int));
    if(*ptr == NULL)
        return -1;
    else 
        return 0;
}

int main(void)
{
    int* ptr;
    int isAllocated = allocatingMemory(&ptr);
    if(isAllocated == 0)
    {
        *ptr = 999;
        free(ptr);
    }

    return 0;
}

Если вы работаете с linked lists и говорите, например, что хотите изменить голову. Вы передадите pointer to pointer (обратите внимание, что он не называется двойным указателем) в head node.

person Community    schedule 29.11.2013
comment
Спасибо! Требуется некоторое пояснение. Что это значит, когда указатель передается по значению? - person user3043746; 29.11.2013
comment
Поскольку переменные передаются по значению функциям. Это означает, что значение переменной копируется в аргумент в списке параметров функции. Точно так же указатели также передаются по значению. Это означает, что значение указателя (указатель имеет адрес некоторой ячейки памяти) копируется (адрес копируется) в другой указатель в списке аргументов функции. Они называют это пропуском по адресу. - person ; 29.11.2013

Чтобы изменить память в контексте вызывающего объекта, функция должна иметь указатель на эту память.

Если вызывающая сторона вашей функции имеет пустой список в переменной и выполняет вставку в этот список следующим образом:

struct list *numbers = NULL;

list_insert(numbers, 4711);

тогда, конечно, внутри list_insert() все, что у нас есть, это указатель NULL, поэтому мы не можем изменить значение переменной numbers в контексте вызывающего объекта.

Если, с другой стороны, нам дан указатель на переменную, мы можем изменить переменную.

Тем не менее, гораздо чище (на мой взгляд) вернуть новый заголовок списка, т.е. сделать сигнатуру функции struct list * list_insert(struct list *head, int x);.

person unwind    schedule 29.11.2013
comment
+1 за предложение вернуть новую главу списка. Делает его намного более читабельным. - person anishsane; 29.11.2013
comment
Я бы предпочел лучшее из обоих миров, чтобы вернуть значение И использовать ссылку. В этом случае вы можете использовать функцию в качестве параметра для другой функции, И у вас есть голова для последующего удаления. - person Peter Miehle; 29.11.2013
comment
спасибо расслабься. Если бы я мог проголосовать за ваш ответ, я бы это сделал. Благодарю вас за то, что вы нашли время, чтобы ответить на мой вопрос :) Как бы вы тогда использовали его. h = list_insert(h, 1) например? - person user3043746; 30.11.2013

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

Есть множество вопросов, связанных с этим на SO. например Использование одиночных и двойных указателей в связанных списках, реализованных в C

person azmuhak    schedule 29.11.2013