Предпочтительный метод использовать два имени для вызова одной и той же функции в C - PullRequest
17 голосов
/ 03 апреля 2012

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

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

int my_function (int);


#define my_func my_function

OR

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

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

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

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

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


Edit:

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

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

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

Q. Будут ли пользователи нуждаться в / хотят иметь открытую библиотеку?

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

Ответы [ 5 ]

7 голосов
/ 03 апреля 2012

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

7 голосов
/ 03 апреля 2012

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

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

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

4 голосов
/ 03 апреля 2012

Что вы можете сделать, это.В заголовочном файле (.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 находятся вне тела функции.

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

3 голосов
/ 03 апреля 2012

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

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

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

0 голосов
/ 03 апреля 2012

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

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

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

...