Оценить указатель на функцию при доступе к элементу структуры в C?

#c #patch #theory

#c #исправление #теория

Вопрос:

Мне нужно решить проблему, которая, по сути, просто исчезла бы, если бы я мог заставить элемент структуры оценивать результат функции при обращении. Я не думаю, что я когда-либо видел какие-либо примеры такого поведения — На самом деле у меня есть подозрение, что то, что я ищу, нарушило бы некоторые глубокие правила C, если не программирование в целом. Если это так, я, безусловно, был бы признателен, если бы кто-то с немного большим количеством доказательств / опыта объяснил, почему.

Вот несколько упрощенных кодов в качестве примера:

 /* state.c */

#include "state.h"

state_t state_ctx;
  
 /* state.h */

typedef struct _state_t {
    foo_t foo;
}state_t;

extern state_t state_ctx;

#define ACCESS_STATE(x) (state_ctx.x)
  
 /* main.c */

const bar_t bar{
    .baz = ACCESS_STATE(foo); // Types are compatible
}

  

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

Этот код работает, но моя миссия состоит в том, чтобы позволить переключать контексты из одной переменной состояния в другую. Я могу легко изменить определения состояний на что-то вроде:

 /* state.c */

#include "state.h"

state_t* p_current_state_ctx; // Now a pointer id's the current state structure
  
 /* state.h */

typedef struct _state_t {
    baz_t foo;
}state_t;

extern state_t* p_current_state_ctx;

#define ACCESS_STATE(x) (p_current_state_ctx->x)
  

Все, что мне нужно сделать для переключения контекстов, это установить указатель текущего состояния. Неплохо. Но одна проблема — списки инициализаторов требуют, чтобы макрос ACCESS_STATE(x) вычислял константу. Я подумал, что было бы замечательно определить функцию, подобную:

 foo_t func_to_get_foo( void ){
    return p_current_state_ctx->foo;
}
  

Чтобы main.c инициализатор можно было переписать как:

 /* main.c */

const bar_t bar{
    .baz = (foo_t)amp;func_to_get_foo; // Trying to get current state's foo
                                    // Obviously this cast isn't generally correct
                                    // and only compiles if the types are pointers
                                    // but still the behavior is wrong
}
  

Потому что указатель на функцию был бы постоянным выражением. Но когда я это написал, мое сердце сжалось, потому что я понял, что, конечно, теперь baz будет просто указатель на func_to_get_foo , а не значение foo, как я фантазировал.

Реальная программа, с которой я работаю, довольно сложная, и я все еще изучаю ее тонкости. Я хочу внести как можно меньше изменений, сохраняя при этом возможность управления несколькими состояниями. Существует множество экземпляров переменных списка инициализаторов, подобных bar примеру, поэтому я бы предпочел избежать написания кода переключения контекста для каждого из них.

Итак, если бы было какое-то волшебство, которое могло бы привести к тому, что результат func_to_get_foo() появится как результат доступа bar.baz , я был бы в восторге. Есть ли у кого-нибудь какие-либо советы о том, как легко это выполнить?

Если нет способа сделать это, то, конечно, мне было бы интересно услышать какую-нибудь теорию о том, почему… Или это просто «это просто не особенность C»?

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

Комментарии:

1. похоже, вы выбрали неправильный язык. попробуйте c .

2.Что не так с определением func_to_get_foo получения state_t* параметра? т.е. func_to_get_foo(amp;bar.baz)

Ответ №1:

Предполагая, что я правильно следую этому, bar это глобальная переменная, и func_to_get_foo ее нельзя свернуть вручную. Это действительно усложняет задачу. На самом деле, нет способа сделать это в переносимом c коде. В старые времена мы вставляли этот материал в начале main() , который работал достаточно хорошо.

С gcc теперь мы можем использовать attribute((constructor))

 bar_t bar; /* cannot declare this const as this might place it in readonly memory */
attribute((constructor))
static void init_bar(){
    bar.baz = func_to_get_foo();
}
  

Будьте осторожны; это работает, только если state_t state_ctx; было инициализировано с помощью const инициализатора, в противном случае этот метод совершенно ненадежен. Инициализаторы атрибутов выполняются в определенном порядке, но это не тот порядок, который вы хотите. Во втором случае мы должны еще больше полагаться на gcc расширения, чтобы воспроизвести c магию iostream следующим образом:

 attribute((constructor))
static void init_bar(){
    init_state();
    bar.baz = func_to_get_foo();
}

/* ... */

state_t state;
static char state_initialized;
attribute((constructor))
void init_state()
{
    if (state_initialized) return;
    state_initialized = 1;
    /* do whatever to fill out state */
}
  

Комментарии:

1. Интересно. Вы также можете назначить последовательности конструктор / деструктор приоритеты, превышающие 100 ( 101, 102, ... ) (gcc резервирует 100 и ниже). В Linux есть немного более радикальный elf_init способ взлома, который позволяет вам записывать имя функции инициализатора непосредственно в .init раздел исполняемого файла, используя встроенную сборку, которая вызывается перед всеми другими конструкторами, что было бы хорошим местом для обеспечения инициализации глобального — но это было бы еще менее переносимо.

2. Если я правильно attribute((constructor)) понимаю, то это сигнализирует компилятору о выполнении функций в списке инициализаторов? Может быть, однажды я найду место для этого… Я вроде ожидал, что вы скажете, что это невозможно в portable C. В любом случае, это, безусловно, отличная штука — спасибо, что поделились!