You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言编译期参数函数指针:事件动作表的灵活条件扩展

Great question! Building a generic event-action library in C that supports flexible, variable-argument conditions is a common challenge—especially when you can’t predict the types or number of parameters your users will need. Let’s walk through three practical, maintainable solutions that balance readability, flexibility, and generality:


1. Closure-like Condition Wrappers (Max Flexibility)

This approach uses a generic condition function type paired with context structs to encapsulate any combination of parameters and types. It’s the most flexible option, supporting arbitrary condition logic without constraints.

Step 1: Define Core Types

First, update your action table structure to include a condition check and its context:

#include <stdbool.h>

// Generic condition function: takes a context pointer and returns true/false
typedef bool (*condition_func_t)(void* context);

// Original action function type
typedef int (*action_func_t)(void);

// Updated action table entry
typedef struct action_entry {
    int event;
    condition_func_t condition; // NULL = no condition (always run)
    void* condition_context;    // Data for the condition check
    action_func_t action;
} action_entry;

Step 2: Create Condition-Specific Contexts & Wrappers

For each condition type (like exceeds or between), define a struct to hold its parameters, plus a wrapper function that executes the logic:

// Context for "exceeds" condition (int version)
typedef struct {
    const int* value_ptr;
    int threshold;
} exceeds_int_ctx_t;

bool exceeds_int_check(void* context) {
    exceeds_int_ctx_t* ctx = (exceeds_int_ctx_t*)context;
    return *ctx->value_ptr > ctx->threshold;
}

// Context for "between" condition (float version)
typedef struct {
    const float* value_ptr;
    float lower_bound;
    float upper_bound;
} between_float_ctx_t;

bool between_float_check(void* context) {
    between_float_ctx_t* ctx = (between_float_ctx_t*)context;
    return (*ctx->value_ptr >= ctx->lower_bound) && (*ctx->value_ptr <= ctx->upper_bound);
}

Step 3: Initialize the Action Table

Now you can populate the table with type-safe, context-rich entries:

// Example state variables
int temperature = 75;
float pressure = 1.2f;

// Pre-allocated context instances (static for lifetime matching the action table)
static exceeds_int_ctx_t temp_over_100 = {&temperature, 100};
static between_float_ctx_t pressure_normal = {&pressure, 0.8f, 1.5f};

action_entry actions[] = {
    {my_event1, exceeds_int_check, &temp_over_100, my_action1},
    {my_event1, between_float_check, &pressure_normal, my_action2},
    {my_event2, NULL, NULL, my_action3}, // No condition: always run
};

Pros & Cons

✅ Fully supports any parameter type/count
✅ Runtime flexibility (you can modify contexts dynamically)
❌ Requires boilerplate for each condition type


2. Preprocessor Macros (Minimize Boilerplate)

To eliminate repetitive context/wrapper code, use C preprocessor macros to auto-generate the necessary structures and functions. This lets users define conditions with a clean, concise syntax.

Step 1: Define Helper Macros

// Macro to generate condition context structs and wrapper functions
#define DEFINE_CONDITION(cond_name, ctx_fields, cond_logic) \
typedef struct { ctx_fields; } cond_name##_ctx_t; \
bool cond_name##_check(void* context) { \
    cond_name##_ctx_t* ctx = (cond_name##_ctx_t*)context; \
    return cond_logic; \
}

// Macro to simplify action table initialization
#define CONDITION(cond_name, ...) cond_name##_check, &(cond_name##_ctx_t){__VA_ARGS__}

Step 2: Define Conditions in One Line

// Define "exceeds_int" condition (no manual struct/wrapper needed)
DEFINE_CONDITION(exceeds_int, const int* value_ptr; int threshold, 
                 *ctx->value_ptr > ctx->threshold)

// Define "between_float" condition
DEFINE_CONDITION(between_float, const float* value_ptr; float lower; float upper,
                 (*ctx->value_ptr >= ctx->lower) && (*ctx->value_ptr <= ctx->upper))

Step 3: Populate the Action Table

Now the syntax is almost exactly what you requested:

action_entry actions[] = {
    {my_event1, CONDITION(exceeds_int, &temperature, 100), my_action1},
    {my_event1, CONDITION(between_float, &pressure, 0.8f, 1.5f), my_action2},
    {my_event3, NULL, NULL, my_action3},
};

Pros & Cons

✅ Near-zero boilerplate for end users
✅ Clean, readable initialization syntax
❌ Macro magic can make debugging harder (use -E to preprocess if needed)


3. C11 _Generic (Type Safety)

If you want to enforce type safety at compile time (e.g., prevent passing a float threshold to an int condition), use C11’s _Generic keyword to dispatch to type-specific condition functions.

Example Implementation

// Type-specific condition logic
bool exceeds_int(const int* val, int threshold) { return *val > threshold; }
bool exceeds_float(const float* val, float threshold) { return *val > threshold; }

// Generic wrapper to select the right function based on pointer type
#define exceeds(val_ptr, threshold) \
    _Generic((val_ptr), \
        const int*: exceeds_int, \
        const float*: exceeds_float)(val_ptr, threshold)

// Integrate with our action table using the closure approach from Option 1
DEFINE_CONDITION(exceeds_generic, const void* val_ptr; long long threshold; bool (*check)(const void*, long long),
                 ctx->check(ctx->val_ptr, ctx->threshold))

// Helper macro for type-safe initialization
#define GENERIC_EXCEEDS(val_ptr, threshold) \
    CONDITION(exceeds_generic, val_ptr, (long long)threshold, \
        _Generic((val_ptr), const int*: (bool(*)(const void*, long long))exceeds_int, \
                               const float*: (bool(*)(const void*, long long))exceeds_float))

Usage in Action Table

action_entry actions[] = {
    {my_event1, GENERIC_EXCEEDS(&temperature, 100), my_action1},
    {my_event1, GENERIC_EXCEEDS(&pressure, 1.5f), my_action2},
};

Pros & Cons

✅ Compile-time type safety (catches mismatched types early)
✅ Works with existing closure/wrapper pattern
❌ Requires C11 support, and adds some complexity


Key Notes for Generic Libraries

  • Memory Management: If you dynamically allocate context structs (e.g., with malloc), ensure users have a way to clean them up when the action table is destroyed.
  • Thread Safety: Make sure condition functions and contexts are thread-safe if your library supports multi-threaded use.
  • No-Condition Cases: Keep supporting NULL conditions for actions that should always run when their event triggers.

内容的提问来源于stack exchange,提问作者L. Heinrichs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:47:44