Дизайн кодирования C - указатели функций?

У меня есть PIC18F46K22 , и я программирую его с помощью компилятора XC8. В конце концов, у меня будет система, похожая на компьютер с stdinи stdout. Таким образом, в основном цикле будет функция, которая проверяет, есть ли новый ввод. Если есть ввод, функция будет вызвана соответствующим образом. Так, например, когда я ввожу A на stdin, PIC будет запускать функцию, подобную function_Aтой function_B, которая вызывается, когда я ввожу B.

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

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

  • Функция mainвызывает функцию типаfunction_A
  • function_Aизменяет глобальную переменную function_addrна указатель адреса функцииin_function_A
  • С этого момента mainвызывает функцию function_addrпри появлении нового ввода.

Так что мне нужна mainфункция, которая проверяет, является ли function_addrона нулем. Если это так, следует вызвать «нормальную» функцию, например function_A. Если это не так, function_addrследует вызвать функцию at. Мне также нужен , function_Aкоторый изменяет function_addrуказатель на in_function_A.

Примечание: когда функция файловой системы должна быть закрыта, is_function_Aее следует просто изменить function_addrна 0.

Итак, в основном мой вопрос заключается в том, как я могу

  • Получить адрес функции (и сохранить его в переменной)
  • Вызов функции по указанному адресу
Не по теме. Принадлежит stackoverflow.com.
Для меня подход конечного автомата гораздо менее рискован, чем работа с указателями на функции. Ваш входной байт передается в структуру конечного автомата (может быть такой же простой, как случай переключения), которая переходит к различным битам кода в зависимости от переменной или переменных состояния. Кроме того, вы не совсем понимаете, откуда берется ваш стандартный ввод (не то, чтобы это действительно имело большое значение, но мне любопытно).
@EJP Я не согласен. То, что есть совпадение, не означает, что оно должно быть на том или ином сайте. Он задает вопросы, связанные с проектированием и программированием низкоуровневых встраиваемых систем, которые кажутся актуальными в любом случае.
Конечные автоматы могут быть основаны на косвенной функции: вызов функции перехода состояния возвращает новую функцию перехода состояния.
@Kortuk Ну, я не согласен. Это вопрос компьютерного программирования, и он не имеет ничего общего с EE, за исключением того, что вы хотите представить, что EE включает в себя компьютерное программирование, что, исходя из очень большого опыта, не является убеждением, которое я разделяю. Существует также вопрос о том, где наиболее сконцентрирован соответствующий опыт, и о том, что не может быть никаких сомнений в том, что SO выигрывает с преимуществом. Таким образом, спрашивать здесь, а не там, по сути, не является рациональным способом исследования.
Давайте обсудим это здесь .
Возможно, вам будет полезно посмотреть примеры указателей на функции в такой книге, как en.wikibooks.org/wiki/C_Programming/…

Ответы (2)

Функция

int f(int x){ .. }

Получить адрес функции (и сохранить его в переменной)

int (*fp)(int) = f;

Вызов функции по указанному адресу

int x = (*fp)( 12 );
+1, очевидный в мире C и ASM, но не столь очевидный для тех, кто начинал с объектно-ориентированных языков.
Вызов fp также может быть записан как 'int x = fp(12);' для повышения читабельности.
Должен признаться, работая сейчас в основном на C# и Java, я иногда скучаю по старым добрым указателям функций из C. Они опасны, когда выполняются неправильно, и большинство задач можно выполнить другими способами, но они , безусловно, полезны в тех немногих случаях, когда они действительно пригодятся.
@MichaelKjörling: Верно. Я постоянно переключаюсь между встроенным программированием на C и C# (даже реализую аналогичный код для связи между устройством и ПК), и каким бы могущественным ни был C#, мне все еще очень нравится C.
@Michiael: я не являюсь активным пользователем Java, но не можете ли вы просто обернуть функцию как метод класса и сохранить (указатель на) этот класс?
@Wouter Вы, наверное, могли бы. Моя точка зрения не в том, что вы не можете сделать что-то подобное, а в том, что фактические указатели могут быть чрезвычайно полезными в некоторых случаях, когда вы не можете этого сделать, или когда для выполнения того же действия другими средствами требуется гораздо больше кода. Я не могу придумать хороший пример из головы, но это, вероятно, больше связано с тем фактом, что в настоящее время я в основном занимаюсь программированием там, где в этом нет необходимости. Указатели (особенно в непроверенном контексте) опасны, и, как известно, их трудно сделать правильно, но очень редко голые указатели — это именно тот инструмент, который вам нужен для выполнения поставленной задачи.
@MichaelKjörling: с C# вы можете использовать переменные типа делегата (особенно типы Func<...> и Action<...>) более или менее так же, как C использует указатели на функции. Это особенно верно, когда вы устанавливаете delgate равным какому-то статическому методу. (Для нестатических методов они не действуют как конструкция указателя на член С++, а вместо этого сохраняют ссылку на какой-то конкретный экземпляр). Также с C#, если ваш код будет работать с полным доверием (например, настольные приложения), вы можете использовать необработанные нефункциональные указатели с «небезопасным» кодом. В Java намеренно отсутствуют обе функции.
fp(12)не увеличивает читаемость по сравнению с (*fp)(12). У них разная читабельность. (*fp)(12)напоминает читателю, что fpэто указатель, а также напоминает объявление fp. На мой взгляд, этот стиль связан со старыми исходными кодами, такими как внутреннее устройство X11 и K&R C. Одна из возможных причин, по которой он может быть связан с K&R C, заключается в том, что в ANSI C можно объявить fpс использованием синтаксиса функции, если fpэто параметр: int func(int fp(double)) {}. В K&R C это было бы int func(fp) int (*fp)(double); { ... }.
Возможно, стоит обсудить последствия того, как указатели функций реализованы на нижнем конце изображения с голым металлом.
@Kortuk Контекст вопроса - PIC18F46K22, вряд ли это PIC низкого уровня. Но на 14-битных ядрах указатель на функцию мало чем отличается от любого другого указателя. На 12-битном ядре все сложно, но я сомневаюсь, что в любом случае можно было бы использовать указатели функций на чипе с двухуровневым стеком.
@WoutervanOoijen Многие пользователи не понимают, что они могут делать что-то очень сумасшедшее. У нас было несколько младших устройств с указателями на функции, потому что они делали это раньше. Есть гораздо более приятные вещи, чем PIC18, просто, возможно, стоит немного расширить, чтобы включить в них последствия для микрочипа. Только предложение.

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

int (*fp)(int) = NULL;

int FunctionA(int x){ 
    return x + x;
}

int FunctionB(int x){ 
    return x * x;
}

void Example(void) {
    int x = 0;

    fp = FunctionA;
    x = fp(3);  /* after this, x == 6 */

    fp = FunctionB;
    x = fp(3);  /* after this, x == 9 */
}

Если не очевидно, что указателю функции действительно назначен целевой адрес функции, целесообразно выполнить проверку NULL.

if (fp != NULL)
    fp(); /* with parameters, if applicable */
+1 за нулевую проверку, но обратите внимание , что компилятор может не гарантировать, что значение по адресу в ОЗУ, которое fpоказывается занимаемым, изначально равно NULL. Я бы сделал int (*fp)(int) = NULL;комбинированное объявление и инициализацию, чтобы быть уверенным - вы не хотите переходить к какой-то случайной ячейке памяти из-за какого-то глупого значения, которое только что там оказалось. Затем просто назначьте fpкак в вашей Example()функции.
Спасибо за комментарий, я добавил инициализацию NULL.
@MichaelKjörling хорошая мысль, но если fpэто «глобальная переменная» (как в приведенном выше коде), то она гарантированно будет инициализирована NULL(без необходимости делать это явно).