对函数调用中的单指针和双指针参数感到困惑

Posted

技术标签:

【中文标题】对函数调用中的单指针和双指针参数感到困惑【英文标题】:Confused by single pointer and double pointer arguments in function calls 【发布时间】:2014-09-26 05:45:07 【问题描述】:

我试图更深入地了解 C 函数中的指针参数。我编写了一个测试程序来尝试查看将单指针与双指针传递给函数然后对其进行修改之间的区别。

我有一个有两个功能的程序。第一个函数modifyMe1 将单个指针作为参数并将a 属性更改为7。第二个函数modifyMe2 将双指针作为参数并将a 属性更改为7。

我预计第一个函数modifyMe1 将是“按值传递”,也就是说,如果我传入我的结构指针,C 将创建它指向的数据的副本。而对于后者,我正在做一个“按引用传递”,它应该修改适当的结构。

但是,当我测试这个程序时,这两个函数似乎都修改了适当的结构。我知道我对指针的性质存在误解,这肯定是论据。有人可以帮我解决这个问题吗?

谢谢!

这是我所拥有的:

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

struct myStructure 
    int a;
    int b;
;

void modifyMe1(struct myStructure *param1) 
    param1->a = 7;


void modifyMe2(struct myStructure **param1) 
    (*param1)->a = 7;


int main(int argc, char *argv[]) 
    struct myStructure *test1;

    test1 = malloc(sizeof(test1));
    test1->a = 5;
    test1->b = 6;

    modifyMe1(test1);

    printf("a: %d, b: %d\n", test1->a, test1->b);

    // set it back to 5
    test1->a = 5;
    printf("reset. a: %d, b: %d\n", test1->a, test1->b);

    modifyMe2(&test1);

    printf("a: %d, b: %d\n", test1->a, test1->b);


    free(test1);
    return 0;

我的输出是:

$ ./a
a: 7, b: 6
reset. a: 5, b: 6
a: 7, b: 6

【问题讨论】:

您可以随时修改指针所指的内容。但是,由于 C 中的所有内容都是按值(副本)传递的,因此您无法更改函数参数,以便调用者可以看到更改,除非您添加间接级别。即,此代码仅修改本地副本; void foo(int *p) p = malloc(size); . 如果使用指针,则按指针传递,而不是按值传递。如果使用双指针,则传递指针的地址。如果要传递值,则不带任何指针传递。 @IvanIvanov:我并不是要学究气,但由于这是一个似乎让所有初学者感到困惑的问题,C 中的 everything 都是按值传递的。这也包括指针。 是的,这很令人困惑,因为当你传递不是指针的值时,它被称为传递值,而当你传递指针的值时,这被称为传递——指针。 @IvanIvanov:我从来没有使用过“pass-by-pointer”这个词。我认为更简单的理解是,在 C 中,一切都是按值传递的。指针在任何方面都不是特殊的。 【参考方案1】:

您可以在 C 中以不同的方式传递参数(Captain Obvious,是的)。

    按价值。然后将其复制到堆栈中。所以函数在函数框架中有变量的本地副本。对参数的任何更改都不会更改传递的值。这就像“只读”模式

    void fooByValue(myStructure_t arg) 
        printf("passed by value %d %d\n", arg.a, arg.b);
        arg.a = 0;
    
    

    按指针传递。然后传递这个变量的地址副本(所以是的,它仍然是按值传递的,但是你传递的是地址的值,而不是整个参数的值)。所以这就像“读写”模式。由于您可以通过其地址访问传递的变量,因此您可以在函数外更改此变量的值。

    void fooByPtr(myStructure_t *arg) 
        printf("passed by pointer %d %d\n", arg->a, arg->b);
        arg->a = 0;
    
    

    但是!还是不能修改指针。

    所以如果你想修改指针,那么你应该将指针传递给指针。这就像“读写修改”模式:

    void fooByDblPtr(myStructure_t **arg) 
        *arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (*arg)->a = 10;
        (*arg)->b = 20;
    
    

    如果这只是指针,那么就会有内存泄漏:

    void fooByDblPtr(myStructure_t *arg) 
        arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (arg)->a = 10;
        (arg)->b = 20;
    
    

    因为这里你为地址的本地副本分配了新地址,而这个参数会在函数完成后被销毁。

    UPD。例如,我们有

    void fooByPtr(myStructure_t *arg) 
        printf("addr inside foo before %p\n", arg);
        arg = (myStructure_t*) malloc(sizeof(myStructure_t));
        (arg)->a = 10;
        (arg)->b = 20;
        printf("addr inside foo after %p\n", arg);
    
    
    void main() 
        myStructure_t *x = NULL;
        x = malloc(sizeof(myStructure_t));
        x->a = 10;
        x->b = 20;
        printf("x addr before = %p\n", x);
        fooByPtr(x);
        printf("x addr after = %p\n", x);
        free(x);
    
    

    内部函数内存被分配,指针被分配给局部变量。调用者仍然保持旧值。函数调用后,我们丢失了内存地址,因此无法释放。

    简短的结论:有一个简单的规则 - 如果需要更改参数,请将指针传递给它。因此,如果要更改指针,请将指针传递给指针。如果要更改双指针,请将指针传递给指针。

    通过指针传递参数也快得多,因为您不需要复制堆栈上的所有值(当然,如果值大于指向该值的指针,否则通过指针传递只读是没有意义的) .但这很危险,因为它可以在函数内部进行修改。因此,您可以使用 const 关键字来保护定义它的参数

    void constFoo(const myStructure_t *arg) 
        arg->a = 10;    //compilation error
        arg->b = 20;    //compilation error
    
    

    当您使用 3rd 方库时,这非常有用:函数签名告诉您函数是否可以修改您的参数。尽管 const 是可选的,但每次可能时都编写 const 关键字是合适的

    传递数组。通常,还发送数组大小(因此,size_t)作为参数。您将数组作为指针传递。

    void foo (int *buf, size_t nbuf) 
        ....
    
    

    有时您可以找到开发人员发送指向对象而不是数组的指针的代码,例如

    void foo (int *buf, size_t size) 
        size_t i;
        for (i = 0; i < size; i++) 
            printf("%d ", buf[i]);
        
    
    
    int main(int argc, char **argv) 
        int a = 10;
        int buf[1] =  10 ;
        foo(buf, 1);
        foo(&a, 1);
    
    

    在这种情况下,一个元素的数组和指向元素的指针的行为相似(尽管它们不一样)。

【讨论】:

嗨,Ivan,与我上面对 Ed 的评论相同,你是说“修改”本质上是改变结构在内存中指向的位置,而我们不允许这样做单指针? 非常好的解释。 太棒了,正是我需要的总结!【参考方案2】:

添加另一个功能:

void modifyMe0(struct myStructure param1)

    param1.a = 7;

这会按值传递结构。函数中所做的修改不会反映在传递给modifyMe0()的参数中。

像这样添加调用代码:

printf("Before 0: a = %d, b = %d\n", test1->a, test1->b);

modifyMe0(*test1);

printf("After  0: a = %d, b = %d\n", test1->a, test1->b);

请注意,调用代码中的前后值是相同的。您还可以将打印添加到您的 modifyMeN() 函数中,以证明其中的值已被修改。

当你将一个指向结构体的指针传递给被调用函数时,调用函数中结构体的值可以被修改。将结构体按值传递给被调用函数时,调用函数中结构体的值不会被修改。

你可以创建另一个函数:

void modifyMe3(struct myStructure **p1)

    free(*p1);
    *p1 = malloc(sizeof(*p1));
    (*p1)->a = -3;
    (*p1)->b = -6;

像这样添加调用代码:

printf("Before 3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);

modifyMe0(*test1);

printf("After  3: address = %p, a = %d, b = %d\n", (void *)test1, test1->a, test1->b);

请注意,调用modifyMe3()后结构的地址发生了变化。

【讨论】:

【参考方案3】:

使用常规参数,例如int,您将获得本地副本 使用指针参数,比如int*,您可以修改它指向的内容 使用双指针参数,比如int**,您可以修改指针本身,即“重新指向”它。

【讨论】:

以上是关于对函数调用中的单指针和双指针参数感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

C#调用DLL,参数是指针,怎么做?

调用一个动态库中的函数,这个函数有一个参数是结构体指针,我如何使用这个指针?

4.6指针变量作参数

C#调用DLL,参数是指针,怎么做?

C++ 指针、引用和函数调用

C#中调用dll,函数参数带指针,如何改写?