我如何理解复杂的函数声明?

时间:2009-09-19 16:08:14

标签: c pointers declaration

我如何理解以下复杂的声明?

char (*(*f())[])();

char (*(*X[3])())[5];

void (*f)(int,void (*)()); 

char far *far *ptr;

typedef void (*pfun)(int,float);

int **(*f)(int**,int**(*)(int **,int **));

11 个答案:

答案 0 :(得分:72)

正如其他人所指出的那样,cdecl是这项工作的正确工具。

如果您想在没有cdecl帮助的情况下理解这种声明,请尝试从内到外和从右到左阅读

从列表 char (*(*X[3])())[5]; 中选择一个随机示例 从X开始,这是声明/定义的标识符(以及最里面的标识符):

char (*(*X[3])())[5];
         ^

X

X[3]
 ^^^

X是 3

的数组
(*X[3])
 ^                /* the parenthesis group the sub-expression */

X是指向

的3 指针的数组
(*X[3])()
       ^^

X是一个3个指向 函数的数组,接受未指定(但固定)的参数数

(*(*X[3])())
 ^                   /* more grouping parenthesis */

X是一个3个指针的数组,用于接受未指定(但固定)数量的参数 并返回指针

(*(*X[3])())[5]
            ^^^

X是一个包含3个指针的数组,用于接受未指定(但固定)的参数,并将指针 返回到5的数组

char (*(*X[3])())[5];
^^^^                ^

X是一个3个指针的数组,用于接受未指定(但固定)数量的参数并返回指向5 char 数组的指针。

答案 1 :(得分:16)

从内部读取它,类似于解决{3+5*[2+3*(x+6*2)]}=0等方程式的方法 - 你首先解决()里面的内容然后[]然后{} }:

char (*(*x())[])()
         ^

这意味着x

char (*(*x())[])()
          ^^

x功能

char (*(*x())[])()
        ^

x会返回指向的指针。

char (*(*x())[])()
       ^    ^^^

x会返回指向数组的指针

char (*(*x())[])()
      ^

x返回指向指针数组的指针

char (*(*x())[])()
     ^         ^^^

x返回指向函数指针数组的指针

char (*(*x())[])()
^^^^

意味着x返回的数组指针指向一个函数指针数组,这些函数指针指向返回 char 的函数。

但是,请使用cdecl。我自己用它来检查我的答案:)。

如果这仍然让您感到困惑(并且可能应该这样),请尝试在一张纸上或您喜欢的文本编辑器中执行相同的操作。通过观察它无法知道它意味着什么。

答案 2 :(得分:13)

听起来像是cdecl工具的工作:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

我四处寻找该工具的官方主页,但找不到真正的主页。在Linux中,您通常可以期望您选择的分发包含该工具,因此我只是安装它以生成上述示例。

答案 3 :(得分:5)

您应该使用cdecl工具。它应该可以在大多数Linux发行版上使用。

e.g。对于这个功能,它会返回你:

char (*(*f())[])(); - 声明f作为函数返回指向返回char的函数的指针数组的指针

void (*f)(int,void (*)()); - 函数指针f的原型。 f是一个带两个参数的函数,第一个是int,第二个是返回void的函数的函数指针。

char far *far *ptr; - ptr是a far pointer到远指针(指向某个字符/字节)。

char (*(*X[3])())[5]; - X是一个包含3个指针的数组,用于接受不确定数量的参数并返回指向5个字符数组的指针。

typedef void (*pfun)(int,float); - 声明函数指针pfun。 pfun是一个带有两个参数的函数,第一个是int,第二个是float类型。该函数没有返回值;

e.g。

void f1(int a, float b)
{ //do something with these numbers
};
顺便说一下,复杂的声明作为最后一个声明并不常见。这是我为此目的而做的一个例子。

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}

答案 4 :(得分:4)

看来你的实际问题是:

  

指针指针的用例是什么?

指向指针的指针往往会在你有一个类型为T的数组时出现,而T本身就是指向其他东西的指针。例如,

  • C中的字符串是什么?通常,它是char *
  • 您是否希望不时有一系列字符串?肯定。
  • 你怎么声明一个? char *x[10]x是一个包含10个指向char的数组,也就是10个字符串。

此时,您可能想知道char **的位置。它从指针算术和C中的数组之间非常密切的关系进入图片。数组名称x(几乎)总是转换为指向它的第一个元素的指针。

  • 第一个元素是什么?一个char *
  • 指向第一个元素的指针是什么?一个char **

在C中,数组E1[E2]被定义为等同于*(E1 + E2)。通常,E1是数组名称,比方说x,自动转换为char **E2是一些索引,比如说3.(此规则也解释了原因3[x]x[3]是相同的。)

指针的指针也会在你想要一个动态分配的某种类型T的数组时出现,它本身就是一个指针。首先,让我们假装我们不知道T是什么类型。

  • 如果我们想要一个动态分配的T矢量,我们需要什么类型? T *vec
  • 为什么?因为我们可以在C中执行指针运算,所以任何T *都可以作为内存中T的连续序列的基础。
  • 我们如何分配此向量,比如n元素? vec = malloc(n * sizeof(T));

这个故事对于任何类型的T都是如此,因此char *也是如此。

  • 如果vecTchar *的类型是什么? char **vec

指针的指针也出现在你有一个需要修改类型为T的参数的函数时,它本身就是一个指针

  • 查看strtol的声明:long strtol(char *s, char **endp, int b)
  • 这是怎么回事? strtol将字符串从基础b转换为整数。它想告诉你它到达字符串的距离。它可能会返回一个包含longchar *的结构,但这不是它的声明方式。
  • 相反,它通过传入在返回之前修改的字符串的地址来返回其第二个结果。
  • 又是什么字符串?哦是的,char *
  • 那么字符串的地址是什么? char **

如果你漫无目的地沿着这条路走,你也可以遇到T ***类型,虽然你几乎总能重构代码以避免它们。

最后,指针的指针出现在链接列表的某些棘手的实现中。考虑C中双重链表的标准声明。

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

这很好用,虽然我不会在这里重现插入/删除功能,但它有一点问题。可以从列表中删除任何节点(或在其之前插入新节点),而不引用列表的头部。好吧,不是任何节点。对于列表的第一个元素,情况并非如此,其中prev将为null。在某些类型的C代码中,这可能会令人烦恼,在这些代码中,您对节点本身的处理比使用列表作为概念更多。这在低级系统代码中相当普遍。

如果我们像这样重写node怎么办:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

在每个节点中,prevp不是指向前一个节点,而是指向前一个节点的next指针。第一个节点怎么样?它是prevphead。如果你绘制一个这样的列表(你来绘制它以了解它是如何工作的)你会看到你可以删除第一个元素或在第一个元素之前插入一个新节点明确引用head的名称。

答案 5 :(得分:2)

Remo.D的阅读功能答案是一个很好的建议。以下是其他人的一些答案。

指向指针的指针的一个用例是当您希望将其传递给将修改指针的函数时。例如:

void foo(char **str, int len)
{
   *str = malloc(len);
}

此外,这可能是一个字符串数组:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

通常情况下,一个不应该使用这个复杂的声明,尽管有时你需要的类型对于像函数指针这样的东西来说非常复杂。在这些情况下,将typedef用于中间类型更具可读性;例如:

typedef void foofun(char**, int);
foofun *foofunptr;

或者,对于您的第一个“函数返回指向函数返回char的指针的数组[]的指针”,您可以这样做:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

在实践中,如果你正在写任何理智的东西,那么很多东西都会有自己的有意义的名字;例如,这些可能是返回名称(某种类型)的函数,因此fun_returning_char可以是name_generator_typearray_of_ptrs_to_fun可以是name_generator_list。所以你可以将它折叠几行,并且只定义那两个typedef - 在任何情况下都可能在其他地方有用。

答案 6 :(得分:2)

  

x:函数返回指针   指向函数的指针数组[]   返回char“ - 呵呵?

你有一个功能

该函数返回一个指针。

该指针指向一个数组。

该数组是一个函数指针数组(或指向函数的指针)

这些函数返回char *。

 what's the use case for a pointer to a pointer?

一个是通过参数促进返回值。

让我们说你有

int function(int *p)
  *p = 123;
   return 0; //success !
}

你称之为

int x;
function(&x);

正如您所看到的,要让function能够修改我们的x,我们必须将指针传递给我们的x。

如果x不是int,而是char *怎么办?好吧,它仍然是相同的,我们必须传递一个指针。指向指针的指针:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

你称之为

char *x;
function(&x); 

答案 7 :(得分:1)

char far *far *ptr;

这是一个过时的Microsoft表单,可以追溯到MS-DOS和非常早期的Windows时代。 SHORT版本是这是一个指向char的远指针的远指针,其中远指针可以指向内存中的任何位置,而不是指向只能指向64K数据段中任何位置的指针。你真的不想知道有关微软内存模型的细节,以解决完全无脑的英特尔80x86分段内存架构。

typedef void (*pfun)(int,float);

这将pfun声明为指向带有int和float的过程的指针的typedef。 您通常会在函数声明或原型中使用它,即

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}

答案 8 :(得分:1)

我们必须从左到右评估所有指针声明语句,从声明中声明指针名称或声明名称开始。

在评估声明时,我们必须从最里面的括号开始。

首先是指针名称或函数名称,然后是parenthersis中最右边的字符,然后是最左边的字符。

示例:

char (*(*f())[])();
         ^

char (*(*f())[])();
         ^^^
In here f is a function name, so we have to start from that.

f()的

char (*(*f())[])();
            ^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:

char (*(*f())[])();
      ^
f() *

我们已经完成了内括号字符,现在我们必须回到这个后面的一个层次:

char (*(*f())[])();

   ------

现在取[],因为它位于当前括号的右侧。

char (*(*f())[])();
             ^^

f() * []

现在拿*,因为右边没有字符。

char (*(*f())[])();
               ^

char (*(*f())[])();
      ^
f() * [] *

char (*(*f())[])();

接下来评估外部的开括号和右括号,它表示一个函数。

f() * [] * ()

char (*(*f())[])();

现在我们可以在语句的末尾添加数据类型。

f() * [] * () char.

char (*(*f())[])();

最终答案:

    f() * [] * () char.

f是一个函数,返回指向函数返回char的指针array []的指针。

答案 9 :(得分:0)

忘记1和2 - 这只是理论上的。

3:这用于程序输入功能int main(int argc, char** argv)。您可以使用char**访问字符串列表。 argv [0] =第一个字符串,argv [1] =第二个字符串,...

答案 10 :(得分:0)

将指针作为参数传递给函数允许该函数更改指向的变量的内容,这对于通过函数返回值以外的方式返回信息非常有用。例如,返回值可能已用于指示错误/成功,或者您可能希望返回多个值。调用代码中的语法是foo(&amp; var),它取var的地址,即指向var的指针。

因此,如果您希望函数更改其内容的变量本身是指针(例如,字符串),则该参数将被声明为指向指针。

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated

int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
    *str1 = some_defined_string ;
    *str2 = alloc_string() ;
    *str3 = "!!" ;

    if (str2 != 0) {
        return 0 ; //successful
    } else {
        return -1 ; //error
    }
}

main()
{
    char *s1 ; //uninitialized
    char *s2 ;
    char *s3 ;

    int success = point_me_to_the_strings(&s1, &s2, &s3) ;

    printf("%s%s%s", s1, s2, s3) ;
}

请注意,main()不为字符串分配任何存储空间,因此point_me_to_the_strings()不会写入str1,str2和str3,就像它们作为指向chars的指针一样传递。相反,point_me_to_the_strings()会更改指针本身,使它们指向不同的位置,并且它可以执行此操作,因为它具有指向它们的指针。