Подтвердить что ты не робот

Предпочтительный метод использования двух имен для вызова той же функции в C

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

1). Можно использовать #defines:

int my_function (int);


#define my_func my_function

ИЛИ

#define my_func(int (a)) my_function(int (a))

2). Вызов встроенных функций - это еще одна возможность:

int my_func(int a) {
    return my_function(a);
}

3). Используйте слабый псевдоним в компоновщике:

int my_func(int a) __attribute__((weak, alias("my_function")));

4). Указатели функций:

int (* const my_func)(int) = my_function;

Причина, по которой мне нужно несколько имен, - это математическая библиотека, которая имеет несколько реализаций того же метода.

Например, мне нужен эффективный метод вычисления квадратного корня из числа скалярных чисел с плавающей запятой. Поэтому я мог просто использовать math.h sqrt(). Это не очень эффективно. Поэтому я пишу один или два других метода, например, используя метод Ньютона. Проблема в том, что каждый метод лучше работает на некоторых процессорах (в моем случае - на микроконтроллерах). Поэтому я хочу, чтобы процесс компиляции выбирал лучший метод.

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

Как вы думаете, лучший метод?


Edit:

Из предложенных решений, по-видимому, есть два важных вопроса, которые я не рассматривал.

Q. Являются ли пользователи работающими в основном на C/С++?

A.. Все известные разработки будут в C/С++ или сборке. Я разрабатываю эту библиотеку для личного использования, в основном для работы над голыми металлическими проектами. Не будет никаких или минимальных функций операционной системы. Существует дистанционная возможность использования этого в полномасштабных операционных системах, что потребует рассмотрения языковых привязок. Поскольку это для личного роста, было бы полезно изучить развитие библиотеки на популярных встроенных операционных системах.

Q. Пользователям будет нужна/нужна открытая библиотека?

A. Пока да. Поскольку это только я, я хочу сделать прямые изменения для каждого процессора, который я использую после тестирования. Здесь будет полезен набор тестов. Таким образом, открытая библиотека несколько помогла бы. Кроме того, каждая "оптимальная реализация" для конкретной функции может иметь неисправные условия. На этом этапе необходимо решить, кто исправляет проблему: пользователь или разработчик библиотеки. Пользователю понадобилась бы открытая библиотека для работы в условиях сбоя. Я и "пользователь", и "дизайнер библиотеки". Было бы лучше, если бы это было возможно. Затем приложения, отличные от реального времени, могут позволить библиотеке решить все проблемы стабильности по мере их появления, но приложения реального времени будут иметь возможность рассматривать алгоритм скорости/пространства и стабильности алгоритма.

4b9b3361

Ответ 1

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

  • Если потребитель вашей библиотеки будет C ish, используйте #define sqrt newton_sqrt для оптимальной читаемости

  • Если некоторые пользователи вашей библиотеки не из разновидности C (думаю, привязки к Dephi,.NET, независимо) пытаются избежать видимого потребителем #defines. Это основной PITA для привязок, поскольку макросы не видны в двоично-встроенных вызовах функций, которые являются наиболее подходящими для привязки.

Ответ 2

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

Ответ 3

Что вы можете сделать, так это. В файле заголовка (.h):

 int function(void);

В исходном файле (.c):

static int function_implementation_a(void);
static int function_implementation_b(void);
static int function_implementation_c(void);

#if ARCH == ARCH_A
int function(void)
{
    return function_implementation_a(); 
}
#elif ARCH == ARCH_B
int function(void)
{
    return function_implementation_b();
}
#else
int function(void)
{
    return function_implementation_c();
}
#endif // ARCH

Статические функции, называемые один раз, часто встраиваются в реализацию. Так, например, с помощью gcc по умолчанию: -finline-functions-called-once включен даже в -O0. Статические функции, которые не вызываются, также обычно не включаются в окончательный двоичный файл.

Обратите внимание, что я не помещаю #if и #else в один тело function, потому что я считаю код более читаемым, если директивы #if находятся вне тела функций.

Обратите внимание, что этот способ лучше работает со встроенным кодом, где библиотеки обычно распространяются в исходной форме.

Ответ 4

Мне обычно нравится решать это с помощью одного объявления в файле заголовка с другим исходным файлом для каждой архитектуры/типа процессора. Тогда у меня просто есть система сборки (обычно GNU make), выберите правильный исходный файл.

Я обычно разбиваю исходное дерево на отдельные каталоги для общего кода и для целевого кода. Например, мой текущий проект имеет каталог toplevel Project1, а под ним находятся каталоги include, common, arm и host. Для arm и host Makefile ищет источник в соответствующем каталоге на основе целевого объекта.

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

Ответ 5

Возможно, вы создали тестовый набор для всех алгоритмов и запустили его на целевой, чтобы определить, какие из них наиболее эффективны, а затем набор тестов автоматически генерирует необходимые псевдонимы компоновщика (метод 3).

Кроме того, простой #define (метод 1), вероятно, самый простой и не будет и никаких потенциальных накладных расходов. Однако он предоставляет пользователю библиотеки возможность реализации нескольких реализаций, что может быть нежелательным.

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