Каков смысл этой части кода? void (* signal (int sig, void (* func) (int))) (int);

Я наткнулся на этот фрагмент кода и полностью потерял интерпретацию его значения.

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

Что такое подробное объяснение кода в строке 2?

Я знаю, что void и int являются типами, * func является указателем на функцию, а скобки для приоритета. Но я до сих пор не получаю (* сигнал...), (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: ошибка: "сигнал" обновлен как другой вид символа /usr/include/signal.h:93: error: предыдущая декларация 'signal' была здесь
TestDeclaration.c: 3: ошибка: "сигнал" обновлен как другой вид символа /usr/include/signal.h:93: error: предыдущее объявление 'signal' было здесь

Теперь я знаю, что оба испытания являются неправильными способами объявления, но почему они неправильны? Почему исходный способ декларации НЕ является повторной декларацией?

Ответ 1

Это объявление функции, принимающей int и указатель на функцию (принятие int return 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).

Ответ 2

Это один из классических примеров того, как могут быть запутаны C-объявления.
Чтобы понять эту декларацию, обычно это помогает ввести typedef:

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

typedef объявляет указатель на функцию (принимает параметр int и не возвращает ничего). Функция signal теперь может рассматриваться как функция, которая принимает два параметра (int и указатель на функцию) и возвращает указатель на функцию.

Это также может быть получено из первоначальной декларации, но это требует некоторой практики. Обычный способ - начинать с идентификатора, который называет самый внешний объект (signal в этом случае):

signal является...

Затем вы читаете до тех пор, пока не найдете непревзойденную закрывающую скобку или конец объявления: void (*signal(int sig, void (*func)(int))(int)

signal - это функция, возвращающая... возврат...

Теперь вы можете выбрать сначала парсинг параметров, или первое значение. Сначала я сделаю возвращаемое значение. Для этого вы читаете назад, чтобы найти подходящую открытую скобку: void (signal( / ... */ ))(int)

`сигнал - это функция, возвращающая указатель на...

Считывание взад и вперед таким образом вы получаете на следующих этапах:

`сигнал - это функция, возвращающая указатель на (функция, возвращающая... возврат...)

`сигнал - это функция, возвращающая указатель на (функция принимает... возвращающий пустоту)

`signal - это функция, возвращающая указатель на a (функция принимает int и возвращающую пустоту)

`signal - это функция, принимающая два параметра: (int) и (указатель на функцию, принимающую int и возвращающую void) и возвращающую указатель на (функция, принимающая int и возвращающую пустоту)

Ответ 3

Возьмем пример того, как можно использовать эту неприятную декларацию:

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

Без слишком многословности можно сказать, что "сигнал" - это функция с двумя параметрами, которые возвращают функцию.

#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 !

Ответ 4

Возвращающий указатель на функцию, которая принимает:

  • целое число как аргумент первого аргумента и
  • указатель на функцию (которая принимает int и возвращает void) в качестве аргумента в качестве второго аргумента.

И принимает целочисленный аргумент.

Ответ 5

Мнемоника, которую я создал много лет назад, что неоценимо, когда вы пытаетесь понять сложные типы:

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, независимо от того, насколько сильно смешаны указатели, массивы и функции.

Это ДЕЙСТВИТЕЛЬНО полезное умение, когда вы пытаетесь понять, как работает какой-то другой код... или даже выдумываете что-то свое, что вы не видели в течение длительного времени.

Но да, лучший способ справиться с чем-либо, что вы не думаете, что люди смогут прочитать с первого взгляда, - это создать его слоями с typedefs. Типы компонентов, вероятно, будут полезны сами собой, и, делая это один шаг за один раз, люди не теряются, пытаясь выяснить, в какой скобке совпадают. Будьте добры к следующему человеку, который касается вашего кода!

Если вы найдете полезную мнемонику, не стесняйтесь процитировать ее в другом месте - просто дайте мне кредит в качестве ее автора, пожалуйста.

Кстати, есть также инструменты "C Объяснения", которые будут анализировать сообщения C и делать перевод на английское описание для вас. Mine был назван CEX по очевидным причинам, но существует много других, и вы должны найти его, если не хотите совершать этот навык в wetware, или если кто-то вручает вам то, что действительно слишком уродливо для вас, чтобы отслеживать.