LINUX.ORG.RU

Функции с переменным числом аргументов: va_arg vs void*

 


0

2

Пытаюсь выбрать наиболее подходящий вариант. Речь идёт о тех случаях, когда количество и тип переменных заранее известно, но вызов должен происходить через некий интерфейс, как, например, через syscall. Фактически, я сейчас пытаюсь определиться между двумя вариантами:

int api1( unsigned int id, ... );
int api2( unsigned int id, const void* arg );

В случае api1 предполагается передавать набор аргументов в зависимости от id.

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

Пока что склоняюсь ко второму способу, потому что, во-первых, структуру можно будет объявить заранее статически, а во-вторых, виден список аргументов. Цена вопроса - размер указателя.

А какой вариант предпочли бы вы? Или может существуют ещё способы?

★★★

Я бы выбрал структуру, такой вариант банально реализовать и поддерживать.

Реализовывал функции с вариантивными аргументами всего несколько раз, без манов и тыкания палочкой не смогу сразу нормально написать :)

Bfgeshka ★★★★★
()

Переменный список аргументов хорош для функций типа printf, когда строка формата парсится в момент выполнения. У вас так вообще может быть третий вариант — массив указателей на функции :)

vodz ★★★★★
()
Ответ на: комментарий от vodz

массив указателей на функции

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

u5er ★★★
() автор топика

Второй вариант потенциально более переносимый, особенно если вызывать через всякие ffi

Цена вопроса - размер указателя.

а так же размер структуры т.к просто аргументы могли бы уместиться в регистры в зависимости от calling conversion

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 1)

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

struct args3 s;
s.a = 1;
s.b = 2;
call_by_index(3, &s);
вместо
call_by_index(3, 1, 2);
это мазохизм.

А вообще такие индексированные вызовы без веских причин делать не надо.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от mittorn

а так же размер структуры

Не уверен. Структура - это набор переменных, которые были бы переданны в функцию в качестве аргументов путём копирования. А если я передаю указатель на структуру, то эти аргументы передаются не через стек и/или регистры, а лежат где-то в памяти, а вместо них передаётся указатель.

u5er ★★★
() автор топика

POSIX функция open работает именно как в варианте 1. В libcurl функция curl_easy_setopt объявлена так же, хотя и рассчитана только на один дополнительный аргумент.

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

anonymous
()
Ответ на: комментарий от firkax

разложить аргументы по полям структуры и вызвать

А разве есть особая разница между раскладыванием аргументов по полям структуры и раскладыванием аргументов при вызове функции?

А вообще такие индексированные вызовы без веских причин делать не надо.

Реализация RPC - это достаточно веская причина?

u5er ★★★
() автор топика
Ответ на: комментарий от u5er

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

aiqu6Ait ★★★★
()
Ответ на: комментарий от u5er

А разве есть особая разница между раскладыванием аргументов по полям структуры и раскладыванием аргументов при вызове функции?

А ты сам не видишь? В 4 раза больше строк и одна лишняя переменная.

Реализация RPC - это достаточно веская причина?

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

firkax ★★★★★
()
Ответ на: комментарий от firkax

А ты сам не видишь? В 4 раза больше строк и одна лишняя переменная.

И какая разница? Ты всё равно всё в одну строчку запишешь, я тебя знаю xD

RPC слишком общее понятие

Пока что это будет LPC, то есть работать на одной машине, но кто знает… Мне нужен механизм, чтоб из одного потока запустить функцию в контексте другого потока и передать ему набор заранее определённых аргументов. Вот сейчас и стою как осёл между двумя стогами сена. Как обычно…

u5er ★★★
() автор топика
Ответ на: комментарий от u5er

И какая разница? Ты всё равно всё в одну строчку запишешь, я тебя знаю xD

Даже если так, эта строчка получится длиннее в несколько раз и некрасиво выглядящей. Вот с теми же сисколлами прекрасный пример: есть низкоуровневый способ запустить сисколл по индексу, а есть libc-обёртки с удобной передачей аргументов.

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

Ну, пересылать информацию о вызове между тредами придётся структурой. А дальше как уже писал: можешь сделать красивые обёртки ко всему. Можешь и не делать. Способ со структурой (поскольку он нативный) будет в любом случае, вопрос только в том будет ли он единственным или нет.

firkax ★★★★★
()
Ответ на: комментарий от aiqu6Ait

Есть и 3-й вариант. Если по id всё равно switch’ить – то может быть сразу объявить X функций с конкретными аргументами?

т.е. вместо

int apifunc(int id, void *arg) {
    switch (id) {
        case 0:
             (struct data0*)arg;
             break;
        case 1:
             (struct data1*)arg;
             break;
        // и т.д.
    }
}

не морочить голову и сразу сделать

int apifunc0(int a, int b);
int apifunc1(char *x, int y);
int apifuncX(struct ku*);
// …
beastie ★★★★★
()
Ответ на: комментарий от u5er

Так все равно придется предусмотреть. Магии нет. Или ты знаешь размер и содержимое void* по id, или у тебя есть понимание что передается в списке аргументов (как в printf - на основе анализа строки).

aiqu6Ait ★★★★
()
Последнее исправление: aiqu6Ait (всего исправлений: 1)
Ответ на: комментарий от aiqu6Ait

Или ты знаешь размер и содержимое void* по id

Я знаю содердимое для конкретного id, но я не могу заранее предусмотреть id для всех возможных случаев. Именно в этом вся суть. Если бы я заранее мог знать все возможные варианты, то я бы сделал бы обычный api.

u5er ★★★
() автор топика

Пока что склоняюсь ко второму способу, потому что, во-первых, структуру можно будет объявить заранее статически, а во-вторых, виден список аргументов. Цена вопроса - размер указателя.

А какой вариант предпочли бы вы?

Второй. В ядре довольно часто встречается

	/* Initialize the spi_controller fields */
	host->num_chipselect = 4;
	host->mode_bits = SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD;
	host->flags = SPI_CONTROLLER_HALF_DUPLEX;
	host->max_speed_hz = AMD_SPI_MAX_HZ;
	host->min_speed_hz = AMD_SPI_MIN_HZ;
	host->setup = amd_spi_host_setup;
	host->transfer_one_message = amd_spi_host_transfer;
	host->mem_ops = &amd_spi_mem_ops;
	host->mem_caps = &amd_spi_mem_caps;
	host->max_transfer_size = amd_spi_max_transfer_size;
	host->max_message_size = amd_spi_max_transfer_size;

	/* Register the controller with SPI framework */
	err = devm_spi_register_controller(dev, host);

или даже

static struct platform_driver amd_spi_driver = {
	.driver = {
		.name = "amd_spi",
		.acpi_match_table = ACPI_PTR(spi_acpi_match),
	},
	.probe = amd_spi_probe,
};

По-моему, вполне читаемо, особенно по сравнению с десятком безымянных параметров

Или может существуют ещё способы?

JSON 🙂

alx777 ★★★
()