繁体   English   中英

指针作为 C 中的函数参数

[英]Pointers as function arguments in C

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

int num = 5;
int *ptr = #

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

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

我在哪里调用函数:

func(&ptr); 

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

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

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

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

传值: void fcn(int foo)

按值传递时,您将获得该值的副本。 如果您更改函数中的值,无论您如何更改,调用者仍会看到原始值。

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

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

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

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

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

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

 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的值,即 adr2。

如果我将adr1用作int * ,则adr2将被错误地视为整数,从而导致一个(可能非常大)的数字。

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

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

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

......有一个意思......

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

你?

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

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

有两个主要的实际差异:

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

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

通常,差异表明函数将分配给指针,并且该分配不应该只是函数的局部。 例如(请记住,这些示例是为了检查 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);

在 func2() 中, foo可能等于 12,但是当 func2() 返回时, b仍将等于 5。在 func1() 中, foo指向一个新的 int,但当 func1() 返回时a仍然a

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

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

int b = 5;
func2 (&b);

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

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

int *a;
foo (&a);

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

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

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

#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 (并且在 main 中使用copystr而不是&copystr ),则会出现分段错误 为什么? 我在代码中加入了一些 printfs,它工作正常,直到strcpy行。 所以我猜测它没有为copystr分配内存。 再说一遍,为什么?
让我们回到通过指针传递的含义。 这意味着您传递了内存位置,您可以直接在那里写入您想要的值。 您可以修改该值,因为您可以访问您的值的内存位置。
类似地,当您将指针传递给指针时,您传递的是指针的内存位置——换言之,您的内存位置的内存位置。 现在(指向指针的指针)您可以更改内存位置,因为您可以在仅使用指针时更改值。
代码起作用的原因是您传递了内存位置的地址。 函数allocstr更改该内存位置的大小,以便它可以容纳“Hello world!” 它返回一个指向该内存位置的指针。
这实际上与传递指针相同,但我们有一个内存位置而不是值。

在 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM