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 и в которой будет виден весь список аргументов.

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

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

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

Не сомневаюсь, но при выборе языка удобство для меня - это не самый главный пункт ;)

Удобство ещё в том, что если нужно в а-ля Си коде можно использовать: std, …

Вообще сам не использую, так как разработал (и пополняю) своё API, которое весьма удобно.

В целом конечно - «на вкус и цвет, товарищей нет».

Пошучу

Как говорил кирпич подойдя к краю крыши другому кирпичу - «Главное, чтобы человек попался хороший».

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

Без сомнения, ты решишь эту задачу с помощью C. И второй вариант безусловно рабочий. Всё дело в том, что будет потом. Насколько это будет читабельно, развиваемо, тестируемо. Вот тут возникают вопросы. И другой язык тут более предпочтителен. При строгой типизации уже при компиляции ты найдешь ошибку, которую в C не нашел бы и она бы выскочила на проде. При явном использовании типов явно видно как добавлять новые типы, при использовании полиморфизма не придется делать страшный switch () из приведения от (void*). Ладно, простите за офтоп.

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

я определю список id и соответствующие данные, которые будут передаваться через указатель

Ну так не поступай как писатели printf (хотя там тоже много чего в оправдание можно сказать).

представь, что у printf() были бы заранее прописаны все возможные варианты и каждый вариант имел бы свой прототип

Вот об том и говорю, что надо мух от котлет отделять на месте, а не послезавтра. Делай тайпхинтинг и наследование структур на все типы пересылаемых данных — айди чего-то там, оно и будет опорой, если не хватит — добавь поле с типом сообщения. Не еби мозги.

deep-purple ★★★★★
()
Ответ на: комментарий от anonymous

В целом конечно - «на вкус и цвет, товарищей нет».

Немного офтопа.

А чем использование C++ в качестве а-ля Си плохо?

Например. перевёл freetype и SDL на C++ и всё ok!

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

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

В теле своей функции ты перечисляешь все ID. Или switch/case, или if - else if - else if. Не суть.

А значит ты предусмотрел абсолютно все ID. В теле своей функции, а не навызывающей стороне. В чём разница?

her_s_gory ★★
()
Ответ на: комментарий от deep-purple

Ну так не поступай как писатели printf (хотя там тоже много чего в оправдание можно сказать).

Пошучу

Разработчики printf наверное не знают иных способов.

С использованием метаданных printf более ээфективен и универсален.

Для run-time такой подход - «шик, блеск, красота».

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

А поди загляни в реализацию ngx_printf и спрячь этот пафос поглубже ))

Смотрел конечно.
Пафоса нет.
Не знают.
Впрочем лишний раз посмотрю исходники (ранее не знали).

anonymous
()
Ответ на: удаленный комментарий

Потому анонимус, если ничего дельного не выдает, идет в жопу. Ну, выдай что-то красивое и по делу — ты же можешь.

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

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

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

Представим, что есть 2 сервиса. Один - локальный проигрыватель музыки, второй удалённый, который отвечает за освещение. У каждого из них есть заголовочник со своим набором id и описанием того, что вместе с этим id передаётся (и передаётся ли вообще). Очевидно, что эти наборы будут полностью отличаться друг от друга, т.к. выполняют абсолютно разные задачи.

Допустим, я сделаю так, как ты решил - пропишу все id и структуры в заголовочнике механизма rpc. Что дальше? А дальше появляется условный вася пупкин и говорит: я хочу сделать автозапуск автомобиля, стоящего во дворе, и управление воротами, как мне это сделать через твой rpc? На что я справедливо развожу руками.

Теперь, когда я расписал всё более подробно, спрошу: почему бы мне не поступить так, как писатели printf, если я не знаю в данный момент список того, что будет передаваться при вызове, но об этом будет известно при использовании этого api?

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

В чём разница?

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

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

Тще (тно). Тут ты никак не уйдешь от реализации «появляется условный вася пупкин и говорит: я хочу сделать автозапуск автомобиля, стоящего во дворе, и управление воротами».

Ты пытаешься переложить личную головную боль на прослойку. Нет — прослоек, которые решат все твои бизнес-задачи — не существует. Тебе придется писать их самому. По каждому кейсу, раз уж ты сам сказал, что «развожу руками».

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

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

Тут ты никак не уйдешь от реализации «появляется условный вася пупкин и говорит: я хочу сделать автозапуск автомобиля, стоящего во дворе, и управление воротами».

Ты пытаешься переложить личную головную боль на прослойку

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

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

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

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

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

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

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

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

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

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

Представим, что есть 2 сервиса. Один - локальный проигрыватель музыки, второй удалённый, который отвечает за освещение. У каждого из них есть заголовочник со своим набором id и описанием того, что вместе с этим id передаётся (и передаётся ли вообще). Очевидно, что эти наборы будут полностью отличаться друг от друга, т.к. выполняют абсолютно разные задачи.

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

В таком случае, если не планируется передавать миллионы событий в секунду, то собственный ASCII-протокол или даже JSON видятся уже не самым худшим решением

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

Код не мой, но идея такая

#include <stdio.h>
#include <string.h>

// Object types
typedef enum {
    TYPE_PERSON = 1,
    TYPE_PRODUCT = 2,
    TYPE_ORDER = 3
} ObjectType;

// Person structure
typedef struct {
    int id;
    char name[32];
    int age;
} Person;

// Product structure  
typedef struct {
    int id;
    char name[32];
    float price;
} Product;

// Header for all serialized objects
typedef struct {
    ObjectType type;
    int version;
    int size;
} ObjectHeader;

// Simple serialization function
unsigned char* serialize_person(Person *p, int *buffer_size) {
    // Calculate total size: header + person data
    int total_size = sizeof(ObjectHeader) + sizeof(Person);
    unsigned char *buffer = malloc(total_size);
    
    // Create header
    ObjectHeader header = {TYPE_PERSON, 1, total_size};
    
    // Copy header to buffer
    memcpy(buffer, &header, sizeof(ObjectHeader));
    
    // Copy person data after header
    memcpy(buffer + sizeof(ObjectHeader), p, sizeof(Person));
    
    *buffer_size = total_size;
    return buffer;
}

// Deserialize with type checking
void* deserialize_object(unsigned char *buffer) {
    ObjectHeader *header = (ObjectHeader*)buffer;
    
    printf("Found object: type=%d, version=%d, size=%d\n", 
           header->type, header->version, header->size);
    
    // Check object type
    switch(header->type) {
        case TYPE_PERSON: {
            Person *p = malloc(sizeof(Person));
            memcpy(p, buffer + sizeof(ObjectHeader), sizeof(Person));
            return p;
        }
        case TYPE_PRODUCT: {
            Product *prod = malloc(sizeof(Product));
            memcpy(prod, buffer + sizeof(ObjectHeader), sizeof(Product));
            return prod;
        }
        default:
            printf("Unknown object type!\n");
            return NULL;
    }
}

int main() {
    // Create a person
    Person person = {1001, "John Doe", 30};
    
    // Serialize person to buffer
    int buffer_size;
    unsigned char *buffer = serialize_person(&person, &buffer_size);
    
    printf("Serialized %d bytes\n", buffer_size);
    
    // Deserialize from buffer
    Person *restored = (Person*)deserialize_object(buffer);
    
    if (restored) {
        printf("Restored: id=%d, name=%s, age=%d\n", 
               restored->id, restored->name, restored->age);
        free(restored);
    }
    
    free(buffer);
    return 0;
}

Пишем некий код стерилизации, как им то известным способом упаковываем данные, а потом их извлекаем. Удобно.

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

Не, это не ко мне. Прочти вот этот пост Функции с переменным числом аргументов: va_arg vs void* (комментарий)

Если такое делать, то этим должен заниматься уровень выше, но не я. Моя задача - организовать вызов и передачу произвольных аргументов «как есть». Как их принимать и обрабатывать стороны «должны решить» заранее.

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

Если я правильно все понял, то тогда вызов должен быть таким

int api3( unsigned int id, const void* arg, const size_t size);

Мы просто передаем в id кусок память, и все, остальное нас не колебет.

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

Или же делать на каждый id (rpc) свою ручку.

int v1_api_save_track(const void* name, const void* ptr, const size_t size);
int v1_api_del_track(const void* name);
список v1_api_list_tracks();

Второе проще.

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

Единственное мне не нравится id, люди в числах обязательно запутаются.

Поэтому для таких вещей пишется набор дефайнов или enum, где числовые константы «прячутся» за буквами.

const size_t size

Какой смысл использовать тут константу?

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

Поэтому для таких вещей пишется набор дефайнов или enum, где числовые константы «прячутся» за буквами.

Какая разница где путать, в вызове или в файле описания этих дефайнов. Представь список из 50 или больше дефайн. Цифру перепутать очень просто.

Какой смысл использовать тут константу?

Это же стандартный способ передать кусок памяти, указатель на начало + его длина.

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

Представь список из 50 или больше дефайн. Цифру перепутать очень просто.

Так пофиг на цифру. Пишешь в заголовке такую штуку

enum{
	CMD_ENABLE_TO,
	CMD_DISABLE_CE,
	CMD_...
};

И всё! Дальше ты забываешь про цифры, а в коде подставляешь сами енумки.

Это же стандартный способ передать кусок памяти, указатель на начало + его длина.

Ты не понял. Я спросил про константу. Например, вот прототип функции
ssize_t write(int fd, const void *buf, size_t count);
Видишь? В ней используется size_t count, а не const size_t count. Я и спрашиваю: почему ты написал const? Я такого ни разу не встречал.

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

Так пофиг на цифру. Пишешь в заголовке такую штуку

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

Ты не понял. Я спросил про константу. Например, вот прототип функции

Я как то привык к такому для C++. Действую уже на автомате. Сейчас не скажу зачем, что-то связанное в переопределение функций.

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

и поэтому они обязательно разойдутся у клиентов.

Неверно. Заголовочник один на всех. Все подключают этот заголовочник и у всех энумки примут одинаковые значения.

будет дополнять новым функционалом от Васи.

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

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