Приведение указателя void во время выполнения

Я пишу программу на C, которая применяет к массиву другой тип квантования в зависимости от переменной среды.

Проблема в том, что мне нужно использовать ту же функцию void foo(void* ptr, quant_t type, ...), чтобы что-то делать с ptr, но мне нужно заранее привести ее к правильному типу.

(quant_t — это объект типа перечисления)

Я пытался сделать

void foo(void* ptr, quant_t type, ...){
switch(type){
case UNIF:{
struct unif* my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
struct kmeans* my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

Но это не работает, поскольку объявление my_ptr находится внутри области действия переключателя.

Итак, я попытался сделать что-то вроде этого:

void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

Но это все еще не работает.

Очевидно, вы хотите my_ptr->a = bar(...); работать с my_ptr с типом my_ptr, определенным во время выполнения. C не имеет для этого возможности. Динамический выбор типа во время выполнения отсутствует, за исключением длины массивов переменной длины. Если a и b имеют фиксированные типы, вы можете написать в switch код, который подготавливает указатели на них на основе перечисления, а затем использовать эти указатели для присвоения им значений.

Eric Postpischil 29.02.2024 13:49

@Saverio Pasqualoni Вместо оператора переключения используйте операторы if-else для обработки определенных указателей в блоках операторов if-else.

Vlad from Moscow 29.02.2024 13:52

Либо my_ptr всегда должен иметь один и тот же тип (например, подструктура внутри структур unif/kmeans) и быть объявлен перед оператором `switch, либо все операции должны выполняться внутри соответствующих операторов case.

nielsen 29.02.2024 13:53

Ваш оператор переключения упрощается до void* my_ptr = ptr;. Неудивительно, что это не помогает.

ikegami 29.02.2024 15:21

Смотрите этот ответ вчерашний

ikegami 29.02.2024 15:23

Напишите разные версии вашей функции и используйте _Generic внутри макроса, чтобы выбрать правильную версию в зависимости от типа первого аргумента.

David Grayson 29.02.2024 16:04

Зависят ли возвращаемые значения bar(...) и baz(...) от type? Не могли бы вы вызвать их и сохранить возвращаемые значения в локальных переменных перед оператором switch (type)?

Ian Abbott 29.02.2024 16:51

Если у вас есть определенное количество указателей разных типов, рассмотрите union разных указателей, а не указатель void. Может оказаться полезным указатель на виртуальную таблицу всех различных функций вместо switch.

Neil 29.02.2024 17:01

Также поместите обычаи внутри переключателя.

M.M 29.02.2024 22:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
9
127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Чтобы ->a работало, компилятор должен знать расположение (помимо прочего) поля относительно указателя. Это смещение должно быть постоянным в C.

Мы можем добиться этой цели, сделав ваши два типа совместимыми с третьим и используя этот третий тип (Base).

typedef struct {
   A a;
   B b;
} Base;

typedef struct {
   Base base;
   …
} Unif;

typedef struct {
   Base base;
   …
} Kmeans;
// `base` doesn't need to be the first field of `Unif` and `Kmeans`
// (unless you want to recuperate the orig ptr from a `Base *` at some point).

void foo( Base *ptr, … ) {
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( &unif->base, … );

Kmeans *kmeans = …;
foo( &kmeans->base, … );

Это менее безопасно, но мы могли бы назвать его и так:

// `base` must be the first field of `Unif` and `Kmeans`.

void foo( Base *ptr, … ) {
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( (Base *)unif, … );

Kmeans *kmeans = …;
foo( (Base *)kmeans, … );

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

// `base` must be the first field of `Unif` and `Kmeans`.

void foo( void *ptr_, … ) {
   Base *ptr = ptr_;
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( unif, … );

Kmeans *kmeans = …;
foo( kmeans, … );

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

@arfneto, Re "А это была бы полезная вещь.", Да, но совершенно не имеет отношения к вопросу. Не имеет значения, является ли функция OP вариативной или нет. Вопрос не в этом! /// Re «Я никогда бы не подумал, что ОП не спрашивал о том, что я только что объяснил». Извините, но я не могу помочь вам с этой проблемой. Я могу только повторить свой предыдущий совет: велика вероятность, что вы ошибаетесь, если вам придется игнорировать весь текст и половину кода в ФП, чтобы ваша интерпретация имела смысл. /// Когда модеры удаляют ваши комментарии не по теме, это не приглашение их репостить.

ikegami 01.03.2024 17:30

Это забавно! Я сам удалил все комментарии, сэр. Это было слишком долго и скучно и ничего не добавляло ни к теме, ни ко мне.

arfneto 02.03.2024 21:25

Но это не работает, поскольку объявление my_ptr находится внутри области действия переключателя.

Это не работает, но не только из-за декларации my_ptr. Нельзя просто пересылать ...

Я покажу пример, который может делать или не делать то, что вы хотите. Я не уверен, что понимаю, что вам нужно. В любом случае, если вам нужен диспактер, основанный на типе и указателе, этот может подойти.
Если функции используют аргументы одного и того же типа, работает простой 5-строчный VFT, индексированный по типу.

Пример

Основы

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void        (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void        (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void     foo(Class_t type, void* ptr, unsigned N, ...);

main для этого примера

Для тестирования:

  • UNIF_t реализует bar. Пример bar здесь получает строку для идентификатора и (вариативного) некоторого int и просто отображает значения.
  • KMEANS_t реализует baz. Пример baz здесь получает строку для идентификатора и (вариативного) некоторого char* и просто отображает значения.
int main(void)
{
    UNIF_t   unif   = {.bar=for_unif};
    KMEANS_t kmeans = {.baz=for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);
    return 0;
}

Логика тривиальна:

  • создается struct каждого вида
  • пара указатель/тип используется для вызова диспетчера foo()
  • диспетчер вызывает необходимый метод

foo()

    void foo(Class_t type, void* ptr, unsigned N, ...)
    {
        va_list parms;
        va_start(parms, N);
        switch (type)
        {
            case UNIF:
            {
                ((UNIF_t*)ptr)->bar("testing for UNIF", N, parms);
                break;
            }
            case KMEANS:
            {
                ((KMEANS_t*)ptr)->baz("testing for KMEANS", N, parms);
                break;
            }
            default:
                fprintf(stderr, "Invalid Type 0x%X!\n", type);
                break;
        };  // switch()
        va_end(parms);
    }

Это своего рода C++ функтор. Здесь написано так, потому что я предполагаю, что в структурах больше данных. Если нет, то простой VFT, индексированный по типу, работает нормально.

Единственное, что здесь не распространено, — это построение va_list для использования во внутренних функциях.

выход

UNIF: id is 'testing for UNIF', 4 args
        args: (1,2,3,4)

KMEANS: id is 'testing for KMEANS', 3 args
 1: 'Stack'
 2: 'Overflow'
 3: 'C'

KMEANS: id is 'testing for KMEANS', 2 args
 1: 'What'
 2: 'if?'

UNIF: id is 'testing for UNIF', 8 args
        args: (8,7,6,5,4,3,2,1)

полный код примера

#include <stdarg.h>
#include <stdio.h>

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);

// functions for testing these 'classes'
void for_unif (const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);

int main(void)
{
    UNIF_t   unif   = {.bar = for_unif};
    KMEANS_t kmeans = {.baz = for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);

    return 0;
}

void for_unif (const char* id, unsigned N, va_list parms)
{
    printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
    for (size_t i = 0; i < N - 1; i += 1)
        printf("%d,", va_arg(parms, int));
    printf("%d)\n", va_arg(parms, int));
    return;
}

void for_kmeans(const char* id, unsigned N, va_list parms)
{
    printf("\nKMEANS: id is '%s', %d args\n", id, N);
    for (unsigned i = 0; i < N; i += 1)
        printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
    return;
}

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    switch (type)
    {
        case UNIF:
        {
            ((UNIF_t*)ptr)
                ->bar("testing for UNIF", N, parms);
            break;
        }
        case KMEANS:
        {
            ((KMEANS_t*)ptr)
                ->baz("testing for KMEANS", N, parms);
            break;
        }
        default:
            fprintf(stderr, "Invalid Type 0x%X!\n", type);
            break;
    };  // switch()
    va_end(parms);
}

больше из исходного вопроса

void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

если my_ptr есть void*, то my_ptr->a или my_ptr_b не имеют смысла.

Поскольку bar и baz имеют одинаковую подпись, если все, что вы можете, это просто использовать my_ptr для вызова функции внутри struct unif или struct kmeans, в зависимости от типа, это может быть даже проще, фактически однострочным: F[type](descr[type], N, parms); используя foo, как показано ниже:

foo() как однострочник

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    // clang-format off
    const char* descr[] = {
        [UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
        void(*F[])(const char* id, unsigned N, va_list parms) = {
        [UNIF]   = ((UNIF_t*)ptr)->bar,
        [KMEANS] = ((KMEANS_t*)ptr)->baz
    };
 
    F[type](descr[type], N, parms);
    va_end(parms);
}

Если внутри structs больше ничего не нужно и нужен только метод, это всего лишь таблица функций, индексированная type: она работает так же, как в примере выше.

пример 2

#include <stdarg.h>
#include <stdio.h>

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);

// functions for testing these 'classes'
void for_unif (const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);

int main(void)
{
    UNIF_t   unif   = {.bar = for_unif};
    KMEANS_t kmeans = {.baz = for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);

    return 0;
}

void for_unif (const char* id, unsigned N, va_list parms)
{
    printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
    for (size_t i = 0; i < N - 1; i += 1)
        printf("%d,", va_arg(parms, int));
    printf("%d)\n", va_arg(parms, int));
    return;
}

void for_kmeans(const char* id, unsigned N, va_list parms)
{
    printf("\nKMEANS: id is '%s', %d args\n", id, N);
    for (unsigned i = 0; i < N; i += 1)
        printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
    return;
}

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    // clang-format off
    const char* descr[] = {
        [UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
        void(*F[])(const char* id, unsigned N, va_list parms) = {
        [UNIF]   = ((UNIF_t*)ptr)->bar,
        [KMEANS] = ((KMEANS_t*)ptr)->baz
    };
    // clang-format on

    F[type](descr[type], N, parms);
    va_end(parms);
}

https://stackoverflow.com/questions/78081529/runtime-cast-of-void-pointer

Похоже, вы запутались по пути и в итоге ответили совсем на другой вопрос, чем тот, который был задан. ОП хочет сделать ((UNIF_t*)ptr)->a = bar(); ((UNIF_t*)ptr)->b = baz(); или ((KMEANS_t*)ptr)->a = bar(); ((KMEANS_t*)ptr)->b = baz(); в зависимости от предоставленного аргумента. Но вы ответили, как называть ((UNIF_t*)ptr)->bar() или ((KMEANS_t*)ptr)->baz() в зависимости от аргумента. Это совершенно другое.

ikegami 29.02.2024 22:39

Я пока не уверен в этом, но считаю, что это то, о чем спрашивает вопрос, такие строки, как foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);. Здесь вы видите type, указатель и вариативную функцию, вызываемую при проверке типа с перенаправленными вариативными параметрами. В любом случае я включил полный исходный код, и автор, возможно, сможет нам сказать...

arfneto 01.03.2024 00:13

Ваш код не поддерживает my_ptr->a = bar(...); my_ptr->b = baz(...);, в этом и весь вопрос. Вы вообще не выполняете задания и вызываете foo и baz условно, а не безусловно.

ikegami 01.03.2024 00:14

Что касается «и задание есть». Было бы неправильно, если бы вы сделали это в main, поскольку это то, что foo должно делать. Но вы этого не делаете. Вы не назначаете a и b в main. Или в foo. a и b даже не существуют в вашем коде.

ikegami 01.03.2024 00:17

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

ikegami 01.03.2024 15:24

На вопрос «вы читаете мой пост?», да. Это не имеет никакого отношения к вопросу. /// Re «Не мог бы ты быть также.», Нет. Сомнений не было. И ОП с тех пор подтвердил. /// Re «Что такое my_ptr->a в исходном сообщении? Что такое a?», поле есть как UNIF_t, так и KMEANS_t. /// Re "Вы заметили, что my_ptr пуст?*", Конечно! Вот почему код не работает. /// Опять же, ОП с тех пор «сказал нам». Ты сказал, что это положит конец. Почему ты продолжаешь?

ikegami 01.03.2024 15:52

@ikegami, как говорил ОП? Он вам лично рассказал, что такое a и b? Итак, вы могли бы написать ответ, который даже близко не скомпилирован? Хорошо, тогда. Виноват.

arfneto 01.03.2024 16:00

На вопрос «как говорил ОП?», проголосовав за и одобрив ответ. /// Re "Он тебе лично сказал, что такое a и b?", нет, это в вопросе.

ikegami 01.03.2024 16:01

Другие вопросы по теме