Вопрос о функции указателя в C - PullRequest
6 голосов
/ 16 апреля 2009

Есть следующие объявления:

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));
int numcmp(char *, char *);
int strcmp(char *s, char *t);

Тогда где-то в программе происходит следующий вызов:

  qsort((void**) lineptr, 0, nlines-1, 
                    (int (*)(void*,void*))(numeric ? numcmp : strcmp));

(игнорировать первые три аргумента и numeric).

Я спрашиваю, что это:

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

Я понимаю, что qsort ожидает "указатель на функцию, которая получает два void указателя и возвращает int" в качестве 4-го аргумента, но как то, что написано выше, удовлетворяет этому? Мне кажется, что это какой-то актерский состав, потому что он состоит из двух скобок, но это было бы очень странно. Потому что она берет функцию и делает эту функцию «указателем на функцию, которая получает два void указателя и возвращает int». Что бессмысленно.
(Я следовал здесь правилу, что тип type в скобках перед тем, как переменная переводит переменную в этот тип).

Так что я думаю, что я просто ошибаюсь, может быть, кто-то может сказать мне, как это прочитать, каков порядок?

Ответы [ 9 ]

5 голосов
/ 16 апреля 2009

Вы пропустили трюк здесь - часть

(numeric ? numcmp : strcmp)

использует троичный оператор для выбора , какая функция вызывается внутри qsort. Если данные являются числовыми, они используют numcmp. Если нет, он использует strcmp. Более читаемая реализация будет выглядеть так:

int (*comparison_function)(void*,void*) = 
    (int (*)(void*,void*))(numeric ? numcmp : strcmp);
qsort((void**) lineptr, 0, nlines-1, comparison_function);
4 голосов
/ 16 апреля 2009

Вы можете сделать это без приведения указателя функции. Вот как . По моему опыту, в большинстве мест, если вы используете приведение, вы делаете это неправильно.

4 голосов
/ 16 апреля 2009

То, что здесь происходит, действительно является актером. Давайте на секунду проигнорируем троичный и притворимся, что numcmp всегда используется. Для целей этого вопроса функции могут действовать как указатели на функции в C. Поэтому, если вы посмотрите на числовой тип, он на самом деле

(int (*)(int*,int*))

Для того, чтобы это правильно использовалось в qsort, у него должны быть пустые параметры. Поскольку все типы здесь имеют одинаковый размер по отношению к параметрам и возвращаемым типам, можно заменить на другой. Все, что нужно, это приведение, чтобы сделать компилятор счастливым.

(int (*)(void*,void*))(numcmp )
3 голосов
/ 16 апреля 2009

Кто бы ни написал этот фрагмент кода, он пытался быть слишком умным . По его мнению, он, вероятно, думает, что он хороший программист, делая умную «однострочку». На самом деле, он делает код, который будет менее читаемым и с которым трудно работать в течение длительного времени и должен быть переписан в более очевидной форме, похожей на код Харпера Шелби.

Помните поговорку Брайана Кернигана:

Отладка вдвое сложнее, чем запись код в первую очередь. Поэтому, если вы напишите код как умно, насколько это возможно, вы, по определение, недостаточно умное для отладки это.


Я делаю много критичного для производительности кодирования с жесткими сроками в реальном времени ... и я до сих пор не видел места, где подходил бы плотный однострочный.

Я даже возился с компиляцией и проверкой asm, чтобы увидеть, имеет ли однострочник лучшую скомпилированную реализацию asm, но я никогда не находил, что однострочный стоит того.

3 голосов
/ 16 апреля 2009

Как уже отмечали другие, для

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

тогда приведен тип

(int (*)(void*,void*))

и выражение

(numeric ? numcmp : strcmp)

Объявления на C могут быть довольно сложными для чтения, но их можно изучить. Метод состоит в том, чтобы начать с внутренней части и затем пройти один шаг вправо, затем один шаг влево, продолжая вправо, влево, вправо, влево и т. Д., Пока не закончится. Вы не пересекаете скобки, пока все внутри не будет оценено. Например, для приведенного выше типа (*) указывает, что это указатель. Указатель был единственным в скобках, поэтому мы оцениваем его справа. (void*,void*) указывает, что это указатель на функцию с двумя аргументами указателя. Наконец, int указывает тип возвращаемого значения функции. Внешняя скобка делает это типом приведения. Обновление: две более подробные статьи: Правило по часовой стрелке / спирали и Чтение деклараций C: руководство для мистифицированных .

Тем не менее, хорошая новость заключается в том, что, хотя вышесказанное чрезвычайно полезно знать, есть чрезвычайно простой способ обмануть : программа cdecl может конвертировать из C в английское описание и наоборот:

cdecl> explain (int (*)(void*,void*))
cast unknown_name into pointer to function (pointer to void, pointer to void) returning int
cdecl> declare my_var as array 5 of pointer to int
int *my_var[5]
cdecl>

Упражнение: Какая переменная i?

int *(*(*i)[])(int *)

Ответьте rot13 , если на вашем компьютере не установлен cdecl (но вам действительно нужно!):

pqrpy> rkcynva vag *(*(*v)[])(vag *)
qrpyner v nf cbvagre gb neenl bs cbvagre gb shapgvba (cbvagre gb vag) ergheavat cbvagre gb vag
pqrpy>
3 голосов
/ 16 апреля 2009

Обратите внимание, что стандартное определение qsort() включает const:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

Обратите внимание, что для компаратора строк задаются два значения 'char **', а не значения char *.

Я пишу свои компараторы, чтобы в вызывающем коде не требовалось приведение:

#include <stdlib.h>    /* qsort() */
#include <string.h>    /* strcmp() */

int num_cmp(const void *v1, const void *v2)
{
    int i1 = *(const int *)v1;
    int i2 = *(const int *)v2;
    if (i1 < i2)
        return -1;
    else if (i1 > i2)
        return +1;
    else
        return 0;
}

int str_cmp(const void *v1, const void *v2)
{
    const char *s1 = *(const char **)v1;
    const char *s2 = *(const char **)v2;
    return(strcmp(s1, s2));
}

Заставлять людей писать код в кодах, используя ваши функции, ужасно. Не.

Две написанные мной функции соответствуют прототипу функции, требуемому стандартом qsort(). Имя функции, за которым не следуют круглые скобки, эквивалентно указателю на функцию.

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

result = (*pointer_to_function)(arg1, arg2, ...);

В современном стиле написано:

result = pointer_to_function(arg1, arg2, ...);

Лично я нахожу явное разыменование более понятным, но не все с этим согласны.

1 голос
/ 16 апреля 2009

Я бы, наверное, прочитал это так:

typedef int (*PFNCMP)(void *, void *);

PFNCMP comparison_function;

if (numeric)
{
    comparison_function =  numcmp;
}
else
{
    comparison_function = strcmp;
}

qsort((void**) lineptr, 0, nlines-1, comparison_function);

Пример в вопросе имеет явный случай.

0 голосов
/ 16 апреля 2009

И numcmp, и strcmp являются указателями на функции, которые принимают два char* в качестве параметров и возвращают int. Подпрограмма qsort ожидает указатель на функцию, которая принимает два void* в качестве параметров и возвращает int. Отсюда и актерский состав. Это безопасно, поскольку void* действует как общий указатель. Теперь перейдем к чтению декларации: Давайте возьмем вашу strcmp декларацию:

 int strcmp(char *, char *);

Компилятор читает это как strcmp на самом деле:

 int (strcmp)(char *, char *)

функция (в большинстве случаев убывающая до указателя на функцию), которая принимает два char * аргумента. Тип указателя strcmp поэтому:

 int (*)(char *, char *)

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

Аналогично, поскольку аргумент компаратора qsort занимает два void * с и, следовательно, нечетное приведение!

0 голосов
/ 16 апреля 2009

Ваша логика верна, я думаю. Это действительно приведение к «указателю на функцию, которая получает два указателя void и возвращает int», который является обязательным типом по сигнатуре метода.

...