C:为啥指针地址在函数中设置为空,而在main中有一个非空地址? [复制]

Posted

技术标签:

【中文标题】C:为啥指针地址在函数中设置为空,而在main中有一个非空地址? [复制]【英文标题】:C: why does pointer address set to null in function, have a non-null address in main? [duplicate]C:为什么指针地址在函数中设置为空,而在main中有一个非空地址? [复制] 【发布时间】:2018-04-25 16:58:36 【问题描述】:

我是 C 的初学者。我正在做一个创建树形数据结构的任务。我在main 中有一个if,我在执行某些操作之前检查top 是否为NULL。在我创建top pointer 的函数中,它在创建后立即返回NULL(正如预期的那样,我还没有指出任何东西)。但是,当执行返回到main 时,它会返回一个地址,即使我没有为其分配一个地址。所以,我的 if 不像我想要的那样工作。

我注意到,如果我从 main 中将顶部设置为 NULL,那么它会粘住。

我的问题是,当执行返回到main 时,为什么top 的地址会从NULL 变为其他地址?我是否在我的代码中做了一些事情,在不知不觉中将top 指向一个非预期的地址?

//struct for org chart tree
    typedef struct org_tree
        struct employee *top;
     org_tree;

//create org tree
    void CreateTree(org_tree *t)
    
        org_tree *temp;
        temp = malloc(sizeof(org_tree));
        t = temp;
        printf("%p\n", t->top); **here returns (nil) as expected
    

//main program
    int main(void)
    
        FILE *file;
        file = fopen("employee.list", "r");
        printf("file opened\n");
        org_tree t;

        CreateTree(&t);
        printf("%p\n", t.top) **here returns a memory location

        t.top = NULL **if I add this, then if below works.

        char mgr[20], st1[20], st2[20], st3[20], st4[20];

        while(fscanf(file, "%s %s %s %s %s\n", mgr, st1, st2, st3, st4)!=EOF)
        
            employee *m;
            if (t.top !=NULL) **this if not working like I want it to because NULL doesn't "stick" unless set in main.
            
             ///remaining code not relevant to question//
            
         
         ...

【问题讨论】:

难道你需要一个指向指针的指针?也许您只访问指针的本地副本......这意味着您可以获得它所指向的内容 - 但您将无法更改它所指向的内容?我不知道... CreateTree 未按预期设置org_tree t。 C 中的所有内容都是按值传递的。这意味着org_tree t 的本地副本是在CreateTree 内部制作的。您分配了t = temp,但是一旦CreateTree 返回,这两个都超出了范围,实际上会造成内存泄漏,因为没有任何东西指向您malloced 的内存。如果要保存指向此malloced 内存的指针,则必须从函数中返回它,或者传入org_tree** 类型并执行*t = temp; @zappy t的地址是一个指针。在该函数中,创建了一个新的t,它采用传递的t 的值。与将 int 传递给函数完全相同。您在函数中对t 所做的任何更改都不会保留在函数之外。有 2 个不同的ts,但它们指向同一个东西。所以要改变那个东西,你必须取消引用指针。 CreateTree 的任何地方都没有发生这种情况 t 按值传递给CreateTree()。因此,调用者看不到t = temp 的分配。 t 是一个指针这一事实并没有改变它(传递的指针)是按值传递的事实。 但更重要的是,您甚至不需要CreateTree 函数!只要您执行org_tree t;,您就会在自动存储中拥有一个org_tree。无需尝试为它分配内存,实际上尝试甚至是逻辑谬误。如果你为另一个org_tree分配内存,那就是另一个org_tree,而不是原来的org_tree t。 @zappy 没问题,但我什至不知道这是否回答了问题,呵呵,我只是跳到代码,这是我看到的第一个问题。此外,我现在正在打电话,不是一个理想的接听平台。 【参考方案1】:

问题是,当您将某些内容传递给函数时,会生成它的本地副本。现在您正在向函数发送地址。就是CreateTree()t的内容。

被调用函数中的t 是什么? tCreateTree 函数的局部变量。 t 的类型是 org_tree*,这意味着它包含 org_tree 变量的地址。

您正在分配CreateTree() 中的一块内存。您正在将该块的地址分配给 t 变量。但这对被调用的函数没有任何意义。

一旦调用函数的被调用,局部变量t的生命就结束了。

所以回到main(),它仍然是旧的t 变量。没有任何改变(它的价值)。


现在你想在这里实现什么?也许你想动态分配一个节点。

现在让我们看一个有效的例子:-

    org_tree* t;

    CreateTree(&t);

    void CreateTree(org_tree **t)
    
        org_tree *temp;
        temp = malloc(sizeof(org_tree));
        (*t) = temp;
    

现在你能说出被调用函数中t 的内容吗? 它仍然是一个指针变量,内容为地址。谁的地址? 指针变量的地址。更准确地说是org_tree* 变量的地址。

这是什么意思? (*t) 基本上是被调用函数的原始t。这意味着我们现在可以改变它。这就是正在做的事情。 同样,如果我们在被调用函数中更改 t 的值,则该更改不会保留在 callee 函数中。只不过是本地副本而已。


没有**,有没有一种简单的方法可以做到这一点? 有。

    org_tree *t = CreateTree();

    org_tree * CreateTree()
        org_tree *temp;
        temp = malloc(sizeof(org_tree));
        return temp;
    

这是如何工作的? temp 不是局部变量吗? 是的。而在函数结束后temp 就没有生命了。但是它指向的内存地址不是这样的。该值被返回。当函数CreateTree 结束时,动态分配的内存仍然存在。并且当函数执行完成时,该内存不会被释放。


内存泄漏

现在看看这个例子(你写的第一个例子)。

    void CreateTree(org_tree *t)
    
        org_tree *temp;
        temp = malloc(sizeof(org_tree));
        t = temp;
        printf("%p\n", t->top); **here returns (nil) as expected
    

在这里,您已将分配的块的内存地址分配给CreateTree() 中的局部变量tCreateTree() 结束,现在无论如何您都可以访问分配的内存吗? 没有。

这就是为什么前面的函数被称为内存泄漏的原因。你调用这 10000 次你会泄漏大量的内存。

传递指针没什么特别的。它们的特别之处在于,我们引用了它包含的地址,并且对该地址中的内容进行了更改。我们开始认为这是指针魔术。这是一个副作用,您可以利用它来保持函数之间的值变化。

这里不要忘记检查malloc 的返回类型,并在完成处理后释放内存。

【讨论】:

非常有帮助,谢谢!!【参考方案2】:

这是因为你在函数中的top变量是一个局部变量,它的作用域只在函数中有效,对其他函数不可用。

变量 top 被重新声明 n main 函数是一个单独的变量,与函数中的变量无关。它具有非空值,因为它包含垃圾值。

要解决您的问题,您可以将 top 变量设为 global 。 那就是在函数之前声明一次。 您不必在函数和主函数中再次声明它。现在这个变量将被所有函数共享。

【讨论】:

【参考方案3】:

我想添加一个简单的测试用例来演示其他人的说法。

#include <stdio.h>

void foo(int *p) 
  printf("pointer: %ld\n", (long)&p);
  printf("address: %ld\n", (long)p);


int main() 
  int i = 0;
  int* p = &i;
  printf("pointer: %ld\n", (long)&p);
  printf("address: %ld\n", (long)p);
  foo(p);
  return 0;

结果:

pointer: 140736715097888
address: 140736715097884
pointer: 140736715097848
address: 140736715097884

上面的代码打印了一个指针的地址(它被分配的地方)和那个指针的内容(它指向的地址),你可以看到内容是一样的,但地址是不同的。这就是为什么如果你在f 中更改p,这对外部p 没有影响。指针已被复制到f 的堆栈中。

【讨论】:

【参考方案4】:

大多数 cmets 都发现了各种问题。请参阅我的 cmets 与您的原始代码内联,以及下面的重写示例(为简单起见,使用列表而不是树):

//struct for org chart tree
    typedef struct org_tree
        struct employee *top;
     org_tree;

//create org tree
    void CreateTree(org_tree *t)
    
        org_tree *temp;
        temp = malloc(sizeof(org_tree));
        t = temp;
        printf("%p\n", t->top); //here returns (nil) as expected
        // Its not actually expected - the compiler happens to 
        // set it to null but it is actually undefined - you
        // have not yet set t->top to any address (through malloc)
        // or explicitly assigned it as NULL
    

//main program
    int main(void)
    
        FILE *file;
        file = fopen("employee.list", "r");
        printf("file opened\n");
        org_tree t; //This line creates the tree 
                    // as a locally allocated variable 
                    // of type org_tree. The declaration
                    // allocates it though it is yet 
                    // to be assigned.


        //CreateTree(&t);   //this is not required.
                            // It would be if your tree head
                            // was a pointer but it is not

        printf("%p\n", t.top); //**here returns a memory location
        // Through undefined behaviour. Even when you called
        // CreateTree, you reassigned it (t=temp) which 
        // creates a memory hole (the original address 
        // assigned by declaration is lost) 

        t.top = NULL; //**if I add this, then if below works.
        // Because it is now assigned.  

        char mgr[20], st1[20], st2[20], st3[20], st4[20];

        while(fscanf(file, "%s %s %s %s %s\n", mgr, st1, st2, st3, st4)!=EOF)
        
            employee *m;
            if (t.top !=NULL) **this if not working like I want it to because NULL doesn't "stick" unless set in main.
            
             ///remaining code not relevant to question//
            
        

这是一个重写的版本,它通过示例解决了指针问题(尽管注意到它不是处理列表和树的最优雅的方式 - 但希望对解释有用)。显而易见的机会是将 CreateEmployee() 和 AddNodeToList() 结合起来:

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

//There's many more elegant ways to do this, though this
//version extends your approach to provide understanding

// This is also simplified as a linked list, unsorted
// rather than a tree - again just to look at the pointer
// concepts;

// To use a tree a recursive walk is best to avoid complex
// procedural structures plus logic to determine left / right
// branches etc.

typedef struct employee 
    char mgr[20], st1[20], st2[20], st3[20], st4[20];
    struct employee *next_node;
 employee;

//struct for org chart list
typedef struct org_list 
    struct employee *top;
 org_list;

//create org list
org_list *CreateList(void)  // must return it

    org_list *temp;
    temp = malloc(sizeof(org_list));
    //t = temp;

    temp->top = NULL; //needs to be explicit

    printf("%p\n", temp->top); //should be NULL

    return temp; //send back the address in the heap


//create employee
employee *CreateEmployee(employee *emp) 
    emp = malloc(sizeof(employee));
    emp->next_node = NULL;
    return emp;


int AddNodeToList(org_list* list_head, employee* e) 

    if (list_head->top == NULL)  //special case - empty list
        list_head->top = e;
        return 1; //all done
     else 
        employee* temp_ptr;         //temporary pointer to walk the list
        temp_ptr = list_head->top;

        while (temp_ptr->next_node != NULL) 
            temp_ptr = temp_ptr->next_node;
        
        // temp_ptr now points to the last node in the list
        // add the employee

        temp_ptr->next_node = e;
        return 1;
    


//main program
int main(void) 
    FILE *file;
    file = fopen("employee.list", "r");
    printf("file opened\n");    //not necessarily - check (file != NULL)

    org_list *t;    //This line creates _a pointer to_ the list

    t = CreateList();   //you need an address to come back
    // The other way is to use a pointer to a pointer but this
    // gets confusing

    printf("%p\n", t->top); // This is now NULL
    // Note you haven't yet added an employee - just the head

    //t.top = NULL; //already done

    char mgr[20], st1[20], st2[20], st3[20], st4[20];

    while (fscanf(file, "%s %s %s %s %s\n", mgr, st1, st2, st3, st4) != EOF) 
        employee *m;    // this doesn't malloc or assign the underlying struct

        // this does
        m = CreateEmployee(m);

        if (m != NULL) 
            // copy the scanned strings into m->mgr m->st1 etc using strncpy

        
        // but m is still not added to the list yet!
        AddNodeToList(t, m);
    

【讨论】:

非常有帮助,谢谢!

以上是关于C:为啥指针地址在函数中设置为空,而在main中有一个非空地址? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核从开机加电到main函数执行

作为 DataTable 的 gridView.DataSource 在 asp.net 中设置为空

在@NamedQuery (JPA QL 1.0) 中设置为空的参数

shared:ERROR: BINARYEN_ROOT 在 /root/.emscripten 中设置为空值

C语言基础:指针相关概念(指针的算术运算 指针数组指向指针的指针 传递指针给函数 从函数返回指针 )为啥C 语言不支持在调用函数时返回局部变量的地址?

C语言中关于结构体指针为啥不能在函数内赋初值的问题?