35

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

int num = 5;
int *ptr = #

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

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

我在哪里调用函数:

func(&ptr); 

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

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

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

4

7 回答 7

75

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

按值传递:void fcn(int foo)

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

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

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

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

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

于 2013-09-09T12:45:49.103 回答
8

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

于 2013-09-09T12:37:24.497 回答
7

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

int num = 5;
int *ptr = #

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

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

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

我在哪里调用函数:

func(&ptr);

Asptr是指向 int 的指针,&ptr是地址,与int **

采用 a 的函数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星指针),但从逻辑的角度来看,有什么区别?

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

于 2013-09-09T13:24:55.767 回答
2

有两个主要的实际区别:

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

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

于 2013-09-09T14:20:00.380 回答
1

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

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

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 值放入其中。

这就是使用不同论证风格的方式。

于 2013-09-09T12:44:12.530 回答
0

自从有人问这个问题以来已经有一段时间了,但这是我对此的看法。我现在正在尝试学习 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而不是©str ),则会出现分段错误。为什么?我在代码中放了一些 printfs,它工作正常,直到出现strcpy行。所以我猜它没有为copystr分配内存。再说一遍,为什么?
让我们回到指针传递的含义。这意味着您传递内存位置,您可以直接在那里写入您想要的值。您可以修改该值,因为您可以访问您的值的内存位置。
同样,当您将指针传递给指针时,您会传递指针的内存位置 - 换句话说,您的内存位置的内存位置。现在(指向指针的指针)您可以更改内存位置,因为您可以在仅使用指针时更改值。
代码起作用的原因是您传递了内存位置的地址。函数allocstr更改该内存位置的大小,以便它可以保存“Hello world!”。它返回一个指向该内存位置的指针。
它实际上与传递指针相同,但我们有一个内存位置而不是一个值。

于 2018-03-25T11:00:27.297 回答
0

在 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
于 2019-11-02T08:40:08.003 回答