这段代码的含义是什么?void (*signal(int sig, void (*func)(int)))(int);

39

我遇到了这段代码,完全无法理解它的含义。

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

对于第2行代码的详细解释是什么?

我知道voidint都是类型,*func是指向函数的指针,括号用于优先级。但我仍然不理解(*signal ...)、(int)以及整个声明组合在一起的含义。解释得越详细越好。

也许我已经知道这个声明的意义/效果。但我还需要进行更多尝试来帮助我理解正在发生的事情,如下所示:

  1 #include <signal.h>
  2 void (*signal)(int sig, void (*func)(int));
  3 void (*signal)(int);  // then void (signal)(int) again.
  4 //void (*signal(int sig, void (*func)(int)))(int); //break this line into two lines above
  5
  6 int main(){}
在上面的代码中,我将 void (*signal(int sig, void (*func)(int)))(int) 拆分成了两行。在第3行,我尝试了 void (*signal)(int)void (signal)(int) 这两种方式,但都出现了相同的错误结果,指示我正在尝试重新声明 signal

TestDeclaration.c:2: error: 'signal' redeclared as different kind of symbol /usr/include/signal.h:93: error: previous declaration of 'signal' was here
TestDeclaration.c:3: error: 'signal' redeclared as different kind of symbol /usr/include/signal.h:93: error: previous declaration of 'signal' was here

现在我知道这两个尝试的方式都是不正确的声明方法,但是它们为什么是不正确的?原始的声明方式为什么不算是重新声明?

3
谢谢您的认可,我理解了其中的一些内容,而不是完全不懂。 - BoltClock
3
请使用http://www.cdecl.org进行尝试。 - Björn Pollex
2
有趣的是,这个在cdecl.org上会出现语法错误。有人能解释一下吗? - Björn Pollex
3
如果删除参数名称,它就能够运作。 - CB Bailey
显示剩余5条评论
5个回答

45

这是一个函数声明,接受一个整数和一个指向函数的指针(该函数接受一个int类型参数并返回void),并返回一个指向函数的指针(该函数接受一个int类型参数并返回void)。


解释或解读指南

您可以将括号中的所有内容视为单个实体进行解读,然后使用“声明跟随使用”的规则向内部工作。

void (*signal(int sig, void (*func)(int)))(int);

括号中的实体看起来像是一个以int为参数并返回void的函数。

去掉外部部分:

*signal(int sig, void (*func)(int))

所以,signal 接受一些参数并返回可以解引用的东西(由于前导 *),以形成一个接受 int 并返回 void 的函数。

这意味着 signal 是一个返回指向函数(接受 int 并返回 void)的指针的函数。

看看它所需的参数,它接受一个 int(即 sig)和 void (*func)(int),它是一个指向函数(接受 int 并返回 void)的指针。


10
在尝试解析复杂的C声明时,了解顺时针/螺旋规则很有帮助。-http://c-faq.com/decl/spiral.anderson.html - Noufal Ibrahim
1
就我个人而言,我不太喜欢顺时针/螺旋规则。我更喜欢从外向内工作。我知道这不是一种流行的方法,但我用我的方法正确应用语法规则感到更加愉快。 - CB Bailey
有趣的是,上面链接的顺时针/螺旋规则页面包含与 OP 所询问的完全相同的声明。 - Jon Purdy
1
在这个问题的重复中有一个更好的答案:https://dev59.com/1Gkx5IYBdhLWcg3wAvqn#9501054 - nalply

9

这是C语言声明可以变得多么复杂的经典例子之一。
为了理解这个声明,通常有助于引入typedef:

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);

typedef声明了一个指向函数的指针(接受一个int参数并返回void)。 现在,函数signal可以被看作是一个接受两个参数(一个int和一个指向函数的指针)并返回一个指向函数的指针的函数。

这也可以从原始声明中推导出来,但需要一些实践。通常的方法是从命名最外层实体的标识符开始(在这种情况下是signal):

signal是一个...

然后你向右读取,直到找到一个不匹配的右括号或声明的结尾:void (*signal(int sig, void (*func)(int))(int)

signal是一个接受...并返回...的函数

现在你可以选择先解析参数还是先解析返回值。我会先解析返回值。为此,您需要向后阅读以查找匹配的左括号:void (signal( / ... */ ))(int)

signal 是一个函数,接受...并返回指向(接受...并返回...的函数)的指针。

这样来回阅读,您会得到以下连续阶段:

signal 是一个函数,接受...并返回指向(接受...并返回 void 的函数)的指针。

signal 是一个函数,接受...并返回指向(接受 int 并返回 void 的函数)的指针。

signal 是一个函数,接受两个参数:(一个 int)和(一个接受 int 并返回 void 的函数的指针),并返回指向(接受 int 并返回 void 的函数)的指针。


3

很多年前我创造了一种记忆方法,它在理解复杂类型时非常有用:

Remember these rules for C declares
And precedence never will be in doubt
Start with the Suffix, Proceed with the Prefix
And read both sets from the inside, out.

当然,除非括号改变了优先级。

将其应用于此情况:

void (*signal(int sig, void (*func)(int)))(int);

signal is:
  [inside parentheses]
  [suffix ()] a function, whose arguments are
    sig, which is [prefix int] an integer, and
      func, which is:
         [inside parentheses]
           [no suffix within these parens]
           [prefix *] a pointer to
         [suffix ()] a function, whose argument is
           an int
         [no more suffixes]
         [prefix void] and which returns void
         [no more prefixes]
       [no more arguments]
     [prefix *] And which returns a pointer to
     [no more prefixes within these parens]
   [suffix ()] a function, whose argument is
      an int
   [no more suffixes]
   [prefix void] and which returns void.

通过一些练习,你可以轻松做到实时操作:

"Signal is function, whose arguments are:
    sig, an integer,
    and func, a pointer to a function whose argument is an int and which returns void
... which returns a pointer to a function that takes int as an argument and returns void.

抱歉第一次出错了,我有点生疏。

是的,这个助记符(当然不包括括号)适用于所有C声明,无论指针、数组和函数混合得多么糟糕。

当你试图弄清楚别人的代码如何工作时,这是一个非常有用的技能......甚至是为自己长时间没有见过的东西找到答案。

但是,如果你认为人们无法一眼读懂任何内容,更好的方法是使用typedef逐层构建。组件类型本身可能也很有用,一步一步地进行操作可以防止人们在尝试找到哪个括号匹配哪个括号时迷失方向。请善待下一个接触你的代码的人!

如果你发现这个助记符有用,请随意在其他地方引用它,但请注明我是它的作者。

顺便说一句,还有一些"C Explainer"工具,可以解析C声明并将其转换为英文描述。我的工具叫CEX,原因很明显,但许多其他工具也存在,如果你不想将此技能记录下来或者有人交给你一些实在太丑陋的东西,你应该能够找到一个工具。


是的,Start和Suffix以及Proceed和Prefix共享初始辅音是有意为之的;这有助于您记住正确的顺序。 - keshlam

3
让我们以一个例子来说明这个可怕声明是如何被使用的:
void (*signal(int sig, void (*func)(int)))(int);

简单来说,"signal"是一个带有两个参数并返回一个函数的函数。

#include <stdio.h>

// First function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_red(int color)
{
    printf("[car_is_red] Color %d (red) is my favorite color too !\n", color);
}

// Second function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_gray(int color)
{
    printf("[car_is_gray] I don't like the color %d (gray) either !\n", color);
}

// The function taken as second parameter by our principal function
// Note that we can point to it using void (*func)(int)
void show_car_type(int mod)
{
    printf("[show_car_type] Our car has the type: %d\n",mod);
}

/* Our principal function. Takes two parameters, returns a function. */
void (* show_car_attributes(int color, void (*func)(int)) )(int)
{
    printf("[show_car_attributes] Our car has the color: %d\n",color); // Use the first parameter

    int mod = 11;  // Some local variable of our function show_car_attributes()
    func(mod);  // Call the function pointed by the second parameter (i.e. show_car_type() )

    // Depending on color value, return the pointer to one of two functions
    // Note that we do NOT use braces with function names
    if (color == 1)
        return car_is_red;
    else
        return car_is_gray;
    }


//main() function
int main()
{
    int color = 2;   // Declare our color for the car
    void (*f)(int);  // Declare a pointer to a function with one parameter (int)

    f = show_car_attributes(color, show_car_type); // f will take the return 
           // value of our principal function. Stated without braces, the 
           // parameter  "show_car_types" is a function pointer of type 
           // void (*func)(int).

    f(color);  // Call function that was returned by show_car_attributes()

    return 0;
}

让我们看一下输出结果:

如果颜色 = 1

[show_car_attributes] Our car has the color: 1
[show_car_type] Our car has the type: 11
[car_is_red] Color 1 (red) is my favorite color too !

如果颜色等于2
[show_car_attributes] Our car has the color: 2
[show_car_type] Our car has the type: 11
[car_is_gray] I don't like the color 2 (gray) either !

0

返回指向一个函数的指针,该函数有:

  • 一个整数作为第一个参数,
  • 一个函数指针(接受一个整数并返回void)作为第二个参数。

并且需要一个整数参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接