Указатели функции каста

Я пишу функцию, которая получает указатель на функцию сравнения и массив MyStructs и должна сортировать массив в соответствии с функцией сравнения:

void myStructSort(
                  struct MyStruct *arr,
                  int size,
                  int (*comp)(const struct MyStruct *, const struct MyStruct *)) {
  qsort(arr, size, sizeof(struct MyStruct), comp);
}

К сожалению, это не скомпилируется, потому что qsort ожидает, что компаратор получит аргументы void *, а не const struct MyStruct *. Я подумал о нескольких плохих решениях и задался вопросом, что такое правильное решение.

Вариант 1

Вставить comp в int (*)(const void *, const void*). Это компилируется, но это поведение undefined (см. этот вопрос SO).

Вариант 2

Создайте глобальную переменную int (*global_comp)(const struct MyStruct *, const struct MyStruct *) и установите global_comp=comp внутри myStructSort. Затем создайте функцию:

int delegatingComp(const void *a, const void *b) {
  return globalComp((const struct MyStruct *)a, (const struct MyStruct *)b);
}

И в myStructSort вызовите qsort(arr, size, sizeof(struct MyStruct), delegatingComp). Проблема с этим - нечеткая глобальная переменная.

Вариант 3

Повторить qsort. Это функционально безопасная, но очень плохая практика.

Есть ли волшебный идеальный четвертый вариант?

Edit

Я не могу изменить API myStructSort, и я компилирую свой код с помощью gcc c99 -Wall -Wextra -Wvla.

Ответы

Ответ 1

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

Вариант 3 просто неверен, как вы указываете. Нет причин для повторной реализации quicksort и, возможно, ошибки.

Вариант 1 - UB, но он будет работать на любом компиляторе. Если вы выберете этот вариант, обязательно добавьте комментарий.

Я бы также подумал:

Вариант 4. Перепроектируйте интерфейс myStructSort, чтобы взять int (*)(const void *, const void*) или полностью отказаться от него и вызвать qsort напрямую. В принципе отправьте его обратно в архитект, потому что он сделал плохой выбор дизайна.

Ответ 2

Следующий подход работает только для gcc. Это часть расширения gnu. далее обратитесь к https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Nested-Functions.html#Nested-Functions

сначала убедитесь, что прототип qsort находится в такой форме:

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

то вы можете:

void myStructSort(
                  struct MyStruct *arr,
                  int size,
                  int (*comp)(const struct MyStruct *, const struct MyStruct *)) {
  int comparator(const void * a, const void *b) {
    return comp((const struct MyStruct *)a, (const struct MyStruct *)b);
  }
  qsort(arr, size, sizeof *arr, comparator);
}

Но опять же, поскольку он использует расширение gnu, не ожидайте слишком большой переносимости.

О ВАШЕМ КОММЕНТАРИИ: для современных gcc стандартом gnu является значение по умолчанию вместо iso. в частности, lastest gcc должен использовать стандарт gnu11. более старые используют gnu89. поэтому я не знаю о ваших параметрах командной строки, но если -std не установлен, это будет работать.

Ниже приведен пример из info gcc, на всякий случай, когда ссылка мертва. он показывает закрытое использование вложенной функции:

 bar (int *array, int offset, int size)
 {
   int access (int *array, int index)
     { return array[index + offset]; }
   int i;
   /* ... */
   for (i = 0; i < size; i++)
     /* ... */ access (array, i) /* ... */
 }

Ответ 3

Если вы используете gcc, то вы можете использовать функцию qsort_r в glibc с 2.8, что позволяет вам указать функцию компаратора с дополнительным аргументом, предоставленным пользователем:

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

Это, конечно, не переносится, и для этого требуется определить макрос функции:

#define _GNU_SOURCE

(На FreeBSD - и, предположительно, Mac OS X - существует аналогичный, но несовместимый qsort_r; разница заключается в том, что предоставленный пользователем контекстный аргумент предоставляется в качестве первого аргумента функции сравнения, а не последний аргумент.)

Но если у вас есть это, это позволяет вам избежать глобального в опции 2:

/* This struct avoids the issue of casting a function pointer to
 * a void*, which is not guaranteed to work. It might not be
 * necessary, but I know of no guarantees.
 */
typedef struct CompContainer {
   int (*comp_func)(const struct MyStruct *, const struct MyStruct *);
} CompContainer;

int delegatingComp(const void *a, const void *b, void* comp) {
  return ((CompContainer*)comp)->comp_func((const struct MyStruct *)a,
                                           (const struct MyStruct *)b);
}

void myStructSort(
              struct MyStruct *arr,
              int size,
              int (*comp_func)(const struct MyStruct *,
                               const struct MyStruct *)) {
  const CompContainer comp = {comp_func};
  qsort_r(arr, size, sizeof(struct MyStruct), delegatingComp, &comp);
}

(Live on ideone)

Ответ 4

Правильный подход заключается в том, чтобы отбрасывать от void const * до MyStruct const * в функции сравнения.

Это хорошо определено для первого объекта, потому что указатель, который был передан в функцию сравнения, был создан с помощью от MyStruct const * до void const * и отбрасывает указатель на void обратно в исходный тип (и это действительно единственное, что есть).

Для других членов массива предполагается, что отбрасывание void const * до char const *, добавление смещения объекта, сгенерированное путем умножения размера объекта на позицию объекта в массиве и отбрасывание его обратно void const * даст указатель, который можно отбросить на MyStruct const *.

Это смелое предположение, но обычно это получается. Там могут быть угловые случаи, когда это не работает, но в целом компиляторы накладывают любые struct foo на кратное его выравнивание, чтобы гарантировать, что начальные адреса членов массива имеют расстояние sizeof(struct foo).

Указатели функции каста обычно небезопасны и их следует избегать, поскольку разные типы данных могут иметь разные представления - например, void * должен иметь возможность выражать все возможные адреса, поскольку он мог быть преобразован из char *, в то время как a MyStruct * гарантированно имеет несколько наименее значимых битов, которые могут быть выровнены, поскольку любой действительный объект будет выровнен - ​​так что вполне возможно, что соглашение вызова для этих типов может быть другим.

Ответ 5

Единственный разумный вариант - перезаписать созданный интерфейс или создать новый.

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

Короче говоря, с помощью C вы хотите, чтобы ваша функция сортировки имела вид:

void* bubbleSort(void* arr, int (*compareFcn)(void*, void*),
    size_t sizeOfElement, size_t numElements)

И ваша функция сравнения будет иметь вид:

int compareFunction(void *a, void *b);