指针作为 C 中的函数参数

Posted

技术标签:

【中文标题】指针作为 C 中的函数参数【英文标题】:Pointers as function arguments in C 【发布时间】:2013-09-12 22:43:45 【问题描述】:

如果我有这个代码,例如:

int num = 5;
int *ptr = #

以下两个函数有什么区别?

void func(int **foo);
void func(int *foo); 

我在哪里调用函数:

func(&ptr); 

我意识到两者中的前者将一个指向指针的指针作为参数,而第二个只接受一个指针。

如果我传入func(&ptr),我实际上是在传入一个指针。指针指向另一个指针有什么区别?

我相信后者会给出一个不兼容的警告,但看起来细节并不重要,只要你知道你在做什么。似乎为了可读性和理解前者是更好的选择(2星指针),但从逻辑的角度来看,有什么区别?

【问题讨论】:

花几个小时阅读一本好的 C 编程书籍。 (我们不能在几分钟内教你)。还可以阅读一些关于Computer Architecture 和x86 calling conventions 的网页 我了解指针是什么以及它们是如何工作的,我只是想问为什么将指针传递给指针或将指针传递给 int 很重要;两个参数都是指针。无论如何,感谢您的链接。下面的答案很好。 @sherrellbc 嘿,两者都是指针,对。但它们指向哪里,那里又是什么? 这没有意义。您定义了两个具有相同名称的不同函数,func(),一行接一行。那是一个编译器错误,所以你不能传递任何东西,因此之后的一切都是无稽之谈。 [这是一条故意开玩笑的评论,目的是让 sherrellbc 思考为什么人们在提出问题时使用明显的速记方式--以及在回答时]。 ;) 嘿,好久没听到了 :P 所以你最终会接受答案吗?虽然很高兴看到,当有人在接受答案之前需要一些时间以便将一组有价值的问题聚集在一起时,现在 2 年已经足够了,你不也是这么想的吗? :P 你的个人资料告诉我们,你甚至可以再次查看并考虑采取哪一个;) 【参考方案1】:

一个合理的经验法则是,您不能完全更改传递的确切内容,因为调用者会看到更改。传递指针是解决方法。

按值传递:void fcn(int foo)

当通过值传递时,你会得到一个值的副本。如果您更改函数中的值,无论您如何更改,调用者仍会看到原始值。

通过指向值的指针传递:void fcn(int* foo)

通过指针传递给你一个指针的副本——它指向与原始相同的内存位置。此内存位置是存储原件的位置。这使您可以更改指向的值。但是,您无法更改指向数据的实际指针,因为您只收到了指针的副本。

将指针传递给指向值的指针:void fcn(int** foo)

您可以通过将指针传递给指向值的指针来解决上述问题。如上所述,您可以更改该值,以便调用者看到更改,因为它与调用者代码使用的内存位置相同。出于同样的原因,您可以更改指向该值的指针。这使您可以在函数中分配内存并返回它; &arg2 = calloc(len);。您仍然无法将指针更改为指针,因为这是您收到副本的内容。

【讨论】:

是的,这很好地解释了指针如何用于自动和动态生命周期。但我想 OP 想知道的是为什么它是这样对待的,而不是如何。但到目前为止,我还是 +1。 很好的解释。离真正理解如何使用指针又近了一步! 这是解释这个(过去)头痛的最简洁和准确的方式!谢谢【参考方案2】:

区别在于处理器处理代码的操作。在这两种情况下,值本身只是一个地址,这是真的。但是随着地址被取消引用,对于处理器和编译器来说,在取消引用之后知道它将处理什么是很重要的。

【讨论】:

@All thoose downvoters,请向我解释为什么要投票,至少这是问题的答案,OP 问过,他没有问如何使用指向指针的指针,或者如何他们将被 ac 编译器处理! @sherrellbc:你考虑过接受答案吗? :) 我仍然希望,我能够提供帮助;)【参考方案3】:

如果我有这个代码,例如:

int num = 5;
int *ptr = #

以下两个函数有什么区别?:

void func(int **foo);
void func(int *foo);

第一个想要一个指向 int 的指针,第二个想要一个直接指向 int 的指针。

我在哪里调用函数:

func(&ptr);

ptr 是指向 int 的指针,&ptr 是地址,与 int ** 兼容。

采用int * 的函数会做一些与int ** 不同的事情。对话的结果将完全不同,导致未定义的行为,可能导致崩溃。

如果我传入 func(&ptr) 我实际上是在传入一个指针。指针指向另一个指针有什么区别?

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

adr2,我们有一个 int 值,42。

adr1,我们有地址adr2,具有指针大小。

&ptr给我们adr1,ptr,持有&num的值,也就是ad​​r2。

如果我将adr1 用作int *adr2 将被误认为是一个整数,从而导致一个(可能相当大的)数字。

如果我将adr2 用作int **,则第一次取消引用会导致42,这将被误解为地址并可能导致程序崩溃。

int *int ** 之间的区别不仅仅是光学。

相信后者会给出不兼容警告,

...有一个意思...

但似乎细节并不重要,只要你知道自己在做什么。

你呢?

似乎为了可读性和理解,前者是更好的选择(2星指针),但从逻辑的角度来看,有什么区别?

这取决于函数对指针的作用。

【讨论】:

我知道这意味着什么,我也知道我在做什么。太多人完全错误地理解了这一点。我问了一个简单的问题。我不需要向任何人证明我了解正在发生的一切。如果我对某些事情感到困惑,我会将其包含在问题中。我只是问了使用双起始指针的目的是当两者本质上都是指针时是参数。谢谢你的回答。 +1【参考方案4】:

有两个主要的实际区别:

    将指针传递给指针允许函数以调用者可以看到的方式修改该指针的内容。一个经典的例子是strtol() 的第二个参数。在调用strtol() 之后,该指针的内容应指向字符串中未解析以计算long 值的第一个字符。如果您只是将指针传递给strtol(),那么它所做的任何更改都将是本地的,并且无法通知调用者该位置是什么。通过传递该指针的地址,strtol() 可以以调用者可以看到的方式对其进行修改。这就像传递任何其他变量的地址一样。

    更根本的是,编译器需要知道指向的类型才能取消引用。例如,当取消引用 double * 时,编译器将解释(在 double 消耗 8 个字节的实现上)从内存位置开始的 8 个字节作为双精度值。但是,在 32 位实现中,当取消引用 double ** 时,编译器将从该位置开始的 4 个字节解释为另一个双精度的地址。取消引用指针时,指向的类型是编译器关于如何解释该地址的数据的唯一信息,因此了解确切的类型至关重要,这就是为什么认为“他们是都是指针,有什么区别”?

【讨论】:

【参考方案5】:

通常,差异表明函数将分配给指针,并且该分配不应该只是函数的局部。例如(请记住,这些示例是为了检查 foo 的性质,而不是完整的功能,就像原始帖子中的代码应该是真正的工作代码一样):

void func1 (int *foo) 
    foo = malloc (sizeof (int));


int a = 5;
func1 (&a);

类似于

void func2 (int foo) 
    foo = 12;


int b = 5;
func2 (b);

从某种意义上说,foo 在 func2() 中可能等于 12,但是当 func2() 返回时,b 仍将等于 5。在 func1() 中,foo 指向一个新的 int,但@987654326当 func1() 返回时,@ 仍然是 a

如果我们想更改ab 的值怎么办? WRT b,一个普通的 int:

void func3 (int *foo) 
    *foo = 12;
    

int b = 5;
func2 (&b);

会起作用——注意我们需要一个指向 int 的指针。要更改 in 指针的值(即它指向的 int 的地址,而不仅仅是它指向的 int 中的值):

void func4 (int **foo) 
    *foo = malloc (sizeof (int));


int *a;
foo (&a);

'a' 现在指向 func4() 中 malloc 返回的内存。地址&aa 的地址,指向 int 的指针。一个 int 指针包含一个 int 的地址。 func4() 获取一个 int 指针的地址,以便它可以将一个 int 的地址放入该地址,就像 func3() 获取一个 int 的地址,以便它可以将一个新的 int 值放入其中。

这就是使用不同参数样式的方式。

【讨论】:

@Zaibis : 不,你错了。 func4() 是 100% 合法的、正常运行的 C 代码。去试试吧。其他的也都是合法的。地址&aa 的地址,一个指向int 的指针。一个 int 指针包含一个 int 的地址。 func4() 获取一个 int 指针的地址,以便它可以将一个 int 的地址放入该地址,就像 func3() 获取一个 int 的地址,以便它可以将一个新的 int 值放入其中。我做了一个小修改,所以你可以推翻你的投票,希望你会成为一个成年人...... @Zaibis:你仍然错了。 func1() 不会抛出错误,你为什么不实际上尝试一下(而不是假装)。 foo = malloc (sizeof int) 只是重新分配指针foo。它与&a = malloc 不同——它是int *foo = &a; foo = malloc()。它是一个参数并不重要,它是 100% 合法和合乎逻辑的,如果函数更长,可能会达到目的。 你是对的,我错了。对不起,但判断我是否成年......愤怒地否决我的答案并不会让你变得更好。 ;) 为个人排名投票而不是为内容投票是可悲的。 如果我错了,请纠正我,但您的 func1() 似乎只为您的程序提供内存泄漏。您传入一个指向 &a 的指针 - 在函数内部,您将该指针指向 a 并将其分配给一些 int 大小的内存。然后该函数返回到调用函数,您会丢失指针,从而丢失分配内存的内存地址。 @sherrellbc 这正是我告诉他的,但被他否决了。【参考方案6】:

自从有人问这个问题以来已经有一段时间了,但这是我对此的看法。我现在正在尝试学习 C 并且指针总是令人困惑......所以我花时间来澄清指针上的指针,至少对我来说。这是我的想法。 我以here为例:

#include <stdlib.h>
#include <string.h>

int allocstr(int len, char **retptr)

    char *p = malloc(len + 1);  /* +1 for \0 */
    if(p == NULL)
        return 0;
    *retptr = p;
    return 1;


int main()  

    char *string = "Hello, world!";
    char *copystr;
    if(allocstr(strlen(string), &copystr))
        strcpy(copystr, string);
    else    fprintf(stderr, "out of memory\n");
    return 0;

我想知道为什么 allocstr 需要一个双指针。如果是指针,则表示可以传递,返回后会改变... 如果你做这个例子,它工作得很好。但是,如果您将 allocstr 更改为只有 *pointer 而不是 **pointer(并且 copystr 而不是 main 中的 &copystr),您会得到@987654322 @。为什么?我在代码中放了一些 printfs,它工作正常,直到有 strcpy 的那一行。所以我猜它没有为 copystr 分配内存。再说一遍,为什么? 让我们回到指针传递的含义。这意味着您传递内存位置,您可以直接在那里写入您想要的值。您可以修改该值,因为您可以访问您的值的内存位置。 同样,当您将指针传递给指针时,您会传递指针的内存位置 - 换句话说,您的内存位置的内存位置。现在(指向指针的指针)您可以更改内存位置,因为您可以在仅使用指针时更改值。 代码起作用的原因是您传递了内存位置的地址。 allocstr 函数改变了该内存位置的大小,因此它可以容纳“Hello world!”。它返回一个指向该内存位置的指针。 这实际上与传递指针相同,但我们有一个内存位置而不是一个值。

【讨论】:

【参考方案7】:

在 C 中使用链接结构时,例如,简单的链表。考虑您的列表中有一些项目,并且您想添加一个新项目,一种最简单的方法是在项目顺序无关紧要时在开头插入。所以,这是我们简单的项目结构,

typedef struct 
    char *data;    /* item data */
    struct item *next;    /* point to successor */
 item;

并在开头插入,

void insert_item( item **head, char *data) 

    item *new_item;    /* temporary pointer */

    new_item = malloc( sizeof(item) );
    new_item->data = data;

    /* This is how we would set the next item if the parameter was item* head */
    //new_item->next = head;

    /* till this line, nothing useful for passing item** head */
    new_item->next = *head;

    /*
     * Here is where we needed to update the head to point to the newly inserted item at
     * the beginning. which wouldn't be possible if the head parameter was item* head.
     */
    *head = new_item;

你可以这样测试,

item *head;
head = malloc( sizeof(item) );
head->data = "head item data";

printf("before inserting: %s \n", head->data); //before inserting: head item data

insert_item(&head, "new item data");

printf("after inserting: %s \n", head->data); //after inserting: new item data

【讨论】:

以上是关于指针作为 C 中的函数参数的主要内容,如果未能解决你的问题,请参考以下文章

函数指针作为类方法声明中的参数

Linux c 中引用可以做函数参数吗?

c语言 结构体的指针作为函数参数问题

C将int数组指针作为参数传递给函数

c语言中,函数的形参啥时候必须是指针

使用 std::thread 函数 C++11 将指针作为参数传递