【问题标题】:Does passing "pointer to structure" to a function create local copies of it in C?将“指向结构的指针”传递给函数是否会在 C 中创建它的本地副本?
【发布时间】:2015-09-22 11:10:29
【问题描述】:

我有这样的结构

struct node
{
    int data;
    struct node* next;
};

我用它来创建单链表。

我创建了其他功能,例如

int push(struct node* head,int element);

将数据推送到使用节点结构创建的堆栈上。

然后该函数尝试使用代码更新传递给它的struct node* head(它还执行其他操作)

head=(struct node*)malloc(sizeof(struct node));

调用是这样的

struct node* stack;
push(stack,number);

看起来这段代码创建了传递给它的指针的副本。所以我不得不将功能更改为

int push(struct node** head,int element)

*head=(struct node*)malloc(sizeof(struct node));

调用是这样的

struct node* stack;
push(&stack,number);

所以我的问题是,早期的功能是做什么的?如果我想更新指针的原始值,是否需要将struct node** 传递给函数或者我的方法是错误的?

抱歉,我不能提供完整的代码,因为它是一个作业。

【问题讨论】:

  • C 按值传递,即使传递的值是对其他东西的引用。
  • @JohnColeman:澄清:C 不支持引用(该术语在编程中具有明确定义的含义)。您的意思可能是“地址”。
  • @Olaf 指针是 C 实现引用的方式。为什么你认为* 被称为dereference 操作符?我不想陷入术语的困境——但是关于计算机科学引用的维基百科文章提到了指针作为引用类型的一个例子:en.wikipedia.org/wiki/Reference_(computer_science)。但是 - 你可能是对的,来自不同语言的人可能会误解我的意思。
  • @JohnColeman:从技术上讲,您是对的:引用是指针。但它本身不是对象。对引用的所有访问实际上都会影响它指向 的对象。 (普通)指针 otoh 是具有类型和值的普通对象。要访问它指向 的对象,您必须应用一个特殊的函数/运算符(在 C 中:解引用运算符),否则您将访问它的值。这种区分很常见,但你说得对,应该有一个更清晰的术语来区分这两个概念。

标签: c pointers struct


【解决方案1】:

C 总是按值传递。要更改传递给函数的变量,不是传递变量本身,而是传递一个引用(它的地址)。

假设您正在使用旧签名调用您的函数

int push(struct node* head,int element);

struct node *actual_head = NULL;
push(actual_head, 3);

现在在调用 push 之前,您的变量 actual_head 将具有 NULL 的值。

在 push 函数中,一个新的变量head 将被压入堆栈。它将与传递给它的值相同,即NULL

然后当您调用head = malloc(...) 时,您的变量head 将获得一个新值,而不是您想要的actual_head

为了缓解上述问题,您必须将函数的签名更改为

int push(struct node** head,int element);

struct node *actual_head = NULL
push(&actual_head, 3);

现在如果你仔细观察,actual_head 的值是NULL,但是这个指针也存储在某个地方,那个地方就是它的地址&actual_head。我们把这个地址设为1234

现在在 push 函数中,你的变量 head 可以保存一个指针的地址(注意两个 *),将具有 1234 的值

现在,当您执行*head = malloc(...) 时,实际上是在更改位于位置 1234 的对象的值,即您的 actual_head 对象。

【讨论】:

    【解决方案2】:

    C 总是 按值传递参数(即,通过复制它)。这甚至适用于指针,但在这种情况下,复制的是指针本身。大多数时候您使用指针,这很好,因为您对操作指针指向的数据感兴趣。但是,在您的情况下,您想修改指针本身,因此您确实必须使用指向指针的指针。

    【讨论】:

      【解决方案3】:

      是的。

      您的程序的第一个版本是按值传递指针。虽然它传递了一个地址(由pointer to struct 持有),但它没有传递pointer's address - 这是更新值所必需的。

      当您想要更新变量的值时,您必须传递变量的地址。传递一个指针地址,需要一个参数pointer to pointer to type

      在你的情况下,pointer to pointer to struct node

      【讨论】:

        【解决方案4】:

        代码没有按照你的想法做,但不是因为它创建了节点的副本,而是创建了指针的副本。

        尝试打印

        fprintf(stdout, "Address of head: %p\n", (void *) head);
        

        push() 内部和调用函数中。

        你传入的指针和参数在内存中的地址不同,虽然它们都指向同一个地址,在函数返回后,将malloc()的结果存储在其中不会保留。

        你需要像这样传递一个指向指针的指针

        int push(struct node **head, int element)
        {
             /* Ideally, check if `head' is `NULL' and find the tail otherwise */
             *head = malloc(sizeof(**head));
             if (*node == NULL)
                 return SOME_ERROR_VALUE;
             /* Do the rest here */
             return SOME_SUCCESS_VALUE_LIKE_0;
        }
        

        然后调用它,只是

        struct node *head;
        head = NULL;
        push(&head, value);
        /*   ^ take the address of head and pass a pointer with it */
        

        当然,push() 的实现应该非常不同,但我想你会明白的。

        【讨论】:

        • 其实我用类似的printf语句发现了问题。
        • 像这样检查数据有时比使用调试器更有帮助,只是有时。
        【解决方案5】:

        就您的问题而言,每个人所说的一切都是绝对正确的。但是,我认为您还应该考虑设计。您的部分问题是您将堆栈本身与在其上存储数据所需的内部结构混为一谈。您应该有一个堆栈对象和一个节点对象。即

        struct Node
        {
            int data;
            struct Node* next;
        }
        
        struct Stack
        {
            struct Node* head;
        }
        

        然后,您的 push 函数可以在没有任何双重间接的情况下获取指向 Stack 的指针。另外,将某些东西推送到堆栈中间的节点上也没有危险。

        void push(struct Stack* stack, int value)
        {
            struct Node* node = malloc(sizeof node);
            node->data = value;
            node->next = stack->head;
            stack->head = node;
        }
        

        【讨论】:

          【解决方案6】:

          功能

          int push(struct node* head,int element) {
              head=(struct node*)malloc(sizeof(struct node));
          }
          

          分配一些内存并将其丢弃(导致内存泄漏)。

          将“指向结构的指针”传递给函数确实会创建它的本地副本。

          如果要更新指针的原始值,必须将struct node** 传递给函数。 (使用全局变量通常被认为是个坏主意)

          【讨论】:

            【解决方案7】:

            当你将stack 传递给你的函数push(struct node* head,int element)

            然后做

            head=(struct node*)malloc(sizeof(struct node));
            

            指针head 将更新到malloc() 分配的内存,而stack 不知道这个内存,因为您刚刚传递了值。(此处未初始化)

            当你传递地址时,你有一个指向指针的指针,它使 push() 内部的更改反映在 stack

            【讨论】:

              【解决方案8】:

              所以我的问题是,早期的函数是做什么的?

              您之前的函数被定义为接收指向对象的指针。您向函数传递了一个未初始化的结构节点指针。函数不能对表示未初始化指针的值做任何事情。所以你的函数被传递了垃圾,但没有造成任何伤害,因为你的函数立即通过使用指向分配内存的指针覆盖它来忽略它。您的函数现在没有使用您传递的任何值,除了临时本地存储。从函数返回后,函数的参数将被丢弃(它们只是副本),并且堆栈变量的值与以前一样,仍未初始化。编译器通常会在变量初始化之前警告您使用它。

              顺便说一句,指向分配内存的指针值在函数返回时也被丢弃/丢失。所以现在内存中会有一个没有引用的位置,因此没有办法释放它,也就是说,你有内存泄漏。

              如果我想更新指针的原始值或者我的方法错误,是否需要将 struct node** 传递给函数?

              是的,有必要传递一个变量的地址,该变量的地址要由被调用的函数填写。它必须被写入接受指向它将提供的数据类型的指针。由于您正在使用指针引用您的对象,并且由于您的函数正在生成指向您的对象的指针,因此您必须将指针传递给指向您的对象的指针。

              或者,您可以将指针作为值从函数返回,例如

              struct node * Function() { return (struct node *)malloc(sizeof(struct node)); }
              

              电话是……

              struct node *stack;
              stack = Function();
              if(stack == NULL) { /* handle failure */ }
              

              所以,你的方法没有错,只是你的实施(和理解)需要努力。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-07-17
                • 2020-04-25
                • 2017-01-22
                • 1970-01-01
                • 2012-04-21
                • 1970-01-01
                • 1970-01-01
                • 2015-03-18
                相关资源
                最近更新 更多