Должен ли я назначить функцию, которая принимает аргументы, указателям на функции, которые принимают пустоту? - PullRequest
0 голосов
/ 27 августа 2018

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

Способ, которым они предлагают реализовать эти команды, заключается в наличии массива структур c, состоящих из указателя функции и const char *. Затем инициализируйте массив команд с именами команд и соответствующими функциями, которые вы хотите вызывать этой командой.

У вас есть пользователь, который выбирает тип строки, затем эта строка сравнивается с символом * в вашем массиве команд, и, если он совпадает с конкретной записью, вызывает соответствующую функцию в структуре.

Вот пример кода.

typedef void(*functionPointerType)(void);

struct commandStruct
{
    char const *name;
    functionPointerType execute;
    char const *help
};

const struct commandStruct commands[] = {
        {"ver", &CmdVersion, "Display firmware version"},
        {"flashTest" &CmdFlashTest, "Runs the flash unit test"}
};

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

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

Я предполагаю, что мой вопрос таков: это совершенно правильная вещь или это что-то вроде "хака"? Разве определенные компиляторы не позволят мне сделать это?

Ответы [ 5 ]

0 голосов
/ 27 августа 2018

Это плохой совет. Вот как это делается на практике.

Определите ваши функции обратного вызова, аналогичные main(), т.е. взяв в качестве аргументов количество строк и строк: int callback(int argc, char *argv[]).

Итак, ваш список команд может быть, например,

static const struct {
    const char *name;
    const char *help;
    int       (*func)(int argc, char *argv[]);
} firmware_cmd[] = {
    { "help",      "help [ command ]", help_func },
    { "ver",       "ver",              display_version },
    { "flashtest", "flashtest",        flash_text },
    { 0 }
};

В анализаторе команд встроенного программного обеспечения / лексере определите некоторые возвращаемые значения определенной функции обратного вызова:

enum {
    FIRMWARE_CMD_OK = 0,
    FIRMWARE_CMD_ARGS,   /* Invalid arguments! */
    FIRMWARE_CMD_HELP,   /* Command help asked */
    /* All others are error/failure codes */
};

Теперь, когда команда parser / lexer вызывает функцию, она выводит дополнительный текст в зависимости от возвращаемого значения:

  • FIRMWARE_CMD_OK: «ОК»

  • FIRMWARE_CMD_ARGS: «Недопустимые аргументы. Запустите« help COMMAND »для просмотра справки или« help »для просмотра полного списка команд."

  • FIRMWARE_CMD_HELP: текст ->help.

  • Все остальные возвращаемые значения: Error (returnvalue)

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

0 голосов
/ 27 августа 2018

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

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

Таким образом, однозначный комментарий вашей книги в лучшем случае немного отстает.

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

typedef void(*functionPointerType)(int argc, char *argv[]);

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

0 голосов
/ 27 августа 2018

Стандарт не допускает этого, так как типы несовместимы.

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

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

0 голосов
/ 27 августа 2018

если бы кто-то хотел передать аргументы функциям [...], он бы выделил их из командной строки и передал бы функции, определенной указателем функции

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

Например.

typedef void (*functionType)(int argc, const char *argv[]);

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

Если ваша программа не компилируется с -Wall -Werror (по крайней мере для опций в стиле GCC), это, вероятно, неверно.

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

Это вполне допустимая вещь ...?

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

0 голосов
/ 27 августа 2018

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

const struct commandStruct commands[] = {
        {"ver", (functionPointerType)CmdVersion, "Display firmware version"},
        {"flashTest" (functionPointerType)CmdFlashTest, "Runs the flash unit test"}
};

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

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

Поэтому, если вы хотите передать аргументы, вам лучше изменить functionPointerType, чтобы он соответствовал прототипу CmdVersion и CmdFlashTest; в качестве дополнительного бонуса вам больше не нужны эти явные приведения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...