匿名函数 return 动态分配的值

Anonymous functions return dynamically allocated values

问题是基于一个设计模式解决方案,在其他语言中很容易实现,但在 C 中很难实现。缩小代码如下。

this answer 的基础上,我正在尝试为匿名函数中动态生成的值找到解决方案。

答案摘录:

int (*max)(int, int) =
({
    int __fn__ (int x, int y) { return x > y ? x : y; }
    __fn__;
});

静态库代码

struct Super{
}

void add(struct Super *(*superRef)()) {
    // cache the reference (in some linked list)

    // later at some point when an event occurs.
    struct Super *super = superRef(); // instantiate and use it.
}

链接的客户代码:图书馆代码的用户

struct Sub{
     struct Super *super;
}

add(({
    struct Sub __fn__() { return malloc(sizeof(struct Sub)); } // error
    __fn__;
}));

错误:

error: passing 'void' to parameter of incompatible type 'struct Sub *(*)()

根据澄清请求,考虑静态库文件中的接收函数接收对结构对象(未实例化)的引用。库从客户端代码接收此对象。

其次,客户端或静态库库不会立即实例化接收到的结构引用。稍后当系统中有通知时,结构引用被调用以实例化并执行其余的东西。

我重复一遍,具体要求是保存对库用户(客户端代码)传递的结构的非实例化引用。

总结

基本上是一个 Runner,它接收指向它缓存的多态工厂方法的指针,稍后调用以实例化并在事件发生时执行。

问题中显示的代码不是标准C,而是GCC支持的GNU C变体。不幸的是,似乎没有 标签来正确指定所涉及的 C 变体。

此外,用例似乎依赖于将特定类型的面向对象范例硬塞进 C 库接口中。这太可怕了,因为它涉及 C 根本没有的假设和特性。 C(和 GNU-C)和 C++ 以及 Objective-C 是不同的编程语言是有原因的。

"functions returning dynamically allocated values" 的简单答案是使用 void *,对于函数指针, (void *)()。请注意,在 POSIX C 中,void * 也可以保存一个函数指针。

更复杂的答案将描述像 GObject 这样的库如何支持 C 中的面向对象范例。

在实践中,尤其是在POSIX C中,使用一个类型标签(通常是int,但可以是任何其他类型)和一个联合体,可以实现基于联合体的多态结构结构的所有结构都具有该类型标记作为相同的第一个元素。此类功能的最常见示例是 struct sockaddr.

基本上,您的头文件定义了一个或多个具有相同初始成员的结构,例如

enum {
    MYOBJECT_TYPE_DOUBLE,
    MYOBJECT_TYPE_VOID_FUNCTION,
};

struct myobject_double {
    int     type;  /* MYOBJECT_TYPE_DOUBLE */
    double  value;
};

struct myobject_void_function {
    int     type;  /* MYOBJECT_TYPE_VOID_FUNCTION */
    void  (*value)();
};

最后,所有结构类型中的联合类型,或带有匿名联合(由 C11 或 GNU-C 提供)的结构类型,

struct myobject {
    union {
        struct { int type; };          /* for direct 'type' member access */ 
        struct myobject_double         as_double;
        struct myobject_void_function  as_void_function;
    };
};

请注意,从技术上讲,只要该联合可见,将任何这些结构类型的任何指针转换为另一种结构类型并访问 type 成员都是有效的(参见 C11 6.5.2.3第 6 页)。完全不需要使用union,定义并可见union就足够了。

不过,为了便于维护(并避免与未阅读 C 标准中该段的语言律师崇拜者争论),我建议使用包含匿名联合的结构作为 "base" 类型在图书馆界面。

例如,库可能会提供一个函数来 return 某些对象的实际大小:

size_t myobject_size(struct myobject *obj)
{
    if (obj) 
        switch (obj->type) {
        case MYOBJECT_TYPE_DOUBLE:        return sizeof (struct myobject_double);
        case MYOBJECT_TYPE_VOID_FUNCTION: return sizeof (struct myobject_void_function);
        }
    errno = EINVAL;
    return 0;
}

在我看来,OP 正在尝试实现一个 factory pattern,其中库函数为对象提供规范(OOP 中的 class)已创建,以及稍后生成这些对象的方法。

在 C 中实现动态类型的唯一方法是通过我上面显示的那种多态性。这意味着未来对象的规范(同样,OOP 中的 class)必须是普通对象本身。

工厂模式本身很容易在标准 C 中实现。库头文件包含例如

#include <stdlib.h>

/*
 * Generic, application-visible stuff
*/

struct any_factory {

    /* Function to create an object */
    void *(*produce)(struct any_factory *);

    /* Function to discard this factory */
    void  (*retire)(struct any_factory *);

    /* Flexible array member; the actual
       size of this structure varies. */
    unsigned long  payload[];
};

static inline void *factory_produce(struct any_factory *factory)
{
    if (factory && factory->produce)
        return factory->produce(factory);

    /* C has no exceptions, but does have thread-local 'errno'.
       The error codes do vary from system to system. */
    errno = EINVAL;
    return NULL;
}

static inline void factory_retire(struct any_factory *factory)
{
    if (factory) {
        if (factory->retire) {
            factory->retire(factory);
        } else {
            /* Optional: Poison function pointers, to easily
                         detect use-after-free bugs. */
            factory->produce = NULL;
            factory->retire = NULL; /* Already NULL, too. */
            /* Free the factory object. */
            free(factory);
        }
    }
}

/*
 * Library function.
 *
 * This one takes a pointer and size in chars, and returns
 * a factory object that produces dynamically allocated
 * copies of the data.
*/

struct any_factory *mem_factory(const void *, const size_t);

其中 factory_produce() 是一个辅助函数,它调用工厂来生成一个对象,并且 factory_retire() 退出 (discards/frees) 工厂本身。除了额外的错误检查,factory_produce(factory) 等同于 (factory)->produce(factory)factory_retire(factory) 等同于 (factory)->retire(factory)

mem_factory(ptr, len) 函数是库提供的工厂函数的示例。它创建了一个工厂,该工厂生成在 mem_factory() 调用时看到的数据的动态分配副本。

库实现本身将类似于

#include <stdlib.h>
#include <string.h>
#include <errno.h>

struct mem_factory {
    void *(*produce)(struct any_factory *);
    void  (*retire)(struct any_factory *);
    size_t         size;
    unsigned char  data[];
};

/* The visibility of this union ensures the initial sequences
   in the structures are compatible; see C11 6.5.2.3p6.
   Essentially, this causes the casts between these structure
   types, for accessing their initial common members, valid. */
union factory_union {
    struct any_factory  any;
    struct mem_factory  mem;
};

static void *mem_producer(struct any_factory *any)
{
    if (any) {
        struct mem_factory *mem = (struct mem_factory *)any;

        /* We return a dynamically allocated copy of the data,
           padded with 8 to 15 zeros.. for no reason. */
        const size_t  size = (mem->size | 7) + 9;
        char         *result;

        result = malloc(size);
        if (!result) {
            errno = ENOMEM;
            return NULL;
        }

        /* Clear the padding. */
        memset(result + size - 16, 0, 16);

        /* Copy the data, if any. */
        if (mem->size)
            memcpy(result, mem->data, size);

        /* Done. */
        return result;
    }

    errno = EINVAL;
    return NULL;
}

static void mem_retirer(struct any_factory *any)
{
    if (any) {
        struct mem_factory *mem = (struct mem_factory *)any;

        mem->produce = NULL;
        mem->retire  = NULL;
        mem->size    = 0;
        free(mem);
    }
}

/* The only exported function:
*/
struct any_factory *mem_factory(const void *src, const size_t len)
{
    struct mem_factory *mem;

    if (len && !src) {
        errno = EINVAL;
        return NULL;
    }

    mem = malloc(len + sizeof (struct mem_factory));
    if (!mem) {
        errno = ENOMEM;
        return NULL;
    }

    mem->produce = mem_producer;
    mem->retire  = mem_retirer;
    mem->size    = len;

    if (len > 0)
        memcpy(mem->data, src, len);

    return (struct any_factory *)mem;
}

本质上,struct any_factory 类型实际上是多态的(不是在应用程序中,而是仅在库中)。它的所有变体(struct mem_factory 这里)有两个共同的初始函数指针。

现在,如果我们检查上面的代码,并考虑工厂模式,您应该意识到函数指针提供的价值非常小:您可以只使用我之前在这个答案中展示的多态类型,并拥有内联生产者和消费者函数根据工厂类型调用特定于子类型的内部函数。 factory.h:

#ifndef   FACTORY_H
#define   FACTORY_H
#include <stdlib.h>

struct factory {
    /* Common member across all factory types */
    const int  type;

    /* Flexible array member to stop applications
       from declaring static factories. */
    const unsigned long  data[];
};

/* Generic producer function */
void *produce(const struct factory *);

/* Generic factory discard function */
void retire(struct factory *);

/*
 * Library functions that return factories.
*/

struct factory  *mem_factory(const void *, const size_t);

#endif /* FACTORY_H */

factory.c:

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "factory.h"

enum {
    INVALID_FACTORY = 0,

    /* List of known factory types */
    MEM_FACTORY,

    /* 1+(the highest known factory type) */
    NUM_FACTORY_TYPES
};

struct mem_factory {
    int     type;
    size_t  size;
    char    data[];
};

/* The visibility of this union ensures the initial sequences
   in the structures are compatible; see C11 6.5.2.3p6.
   Essentially, this causes the casts between these structure
   types, for accessing their initial common members, valid. */
union all_factories {
    struct factory      factory;
    struct mem_factory  mem_factory;
};

/* All factories thus far implemented
   are a single structure dynamically
   allocated, which makes retiring simple.
*/
void retire(struct factory *factory)
{
    if (factory &&
        factory->type > INVALID_FACTORY &&
        factory->type < NUM_FACTORY_TYPES) {
        /* Poison factory type, to make it easier
           to detect use-after-free bugs. */
        factory->type = INVALID_FACTORY;
        free(factory);
    }
}

char *mem_producer(struct mem_factory *mem)
{
    /* As a courtesy for users, return the memory
       padded to a length multiple of 16 chars
       with zeroes. No real reason to do this. */
    const size_t  size = (mem->size | 7) + 9;
    char         *result;   

    result = malloc(size);
    if (!result) {
        errno = ENOMEM;
        return NULL;
    }

    /* Clear padding. */
    memset(result + size - 16, 0, 16);

    /* Copy data, if any. */
    if (mem->size)
        memcpy(result, mem->data, mem->size);

    return result;
}

/* Generic producer function.
   Calls the proper individual producers.
*/
void *factory_producer(struct factory *factory)
{
    if (!factory) {
        errno = EINVAL;
        return NULL;
    }

    switch (factory->type) {

    case mem_factory:
        return mem_producer((struct mem_factory *)factory);

    default:
        errno = EINVAL;
        return NULL;
    }
}

/* Library functions that return factories.
*/
struct factory *mem_factory(const void *ptr, const size_t len)
{
    struct mem_factory *mem;

    if (!ptr && len > 0) {
        errno = EINVAL;
        return NULL;
    }

    mem = malloc(len + sizeof (struct mem_factory));
    if (!mem) {
        errno = ENOMEM;
        return NULL;
    }

    mem->type = MEM_FACTORY;
    mem->size = len;
    if (len > 0)
        memcpy(mem->data, ptr, len);

    return (struct factory *)mem;
}

如果我们查看标准 C 和 POSIX C 库实现,我们会发现这两种方法都在使用。

标准的I/OFILE结构通常包含函数指针,而fopen()fread()fwrite()等函数只是包装器这些。如果 C 库支持类似于 GNU fopencookie().

的接口,情况尤其如此

POSIX.1 套接字,尤其是 struct sockaddr 类型,是此答案中首先显示的多态结构的原始原型。因为他们的接口不支持任何类似于 fopencookie() 的东西(也就是说,重写 send()recv()read()write()close()), 不需要函数指针。

所以,请不要问哪个更合适,因为两者都很常用,而且很大程度上取决于细节。一般来说,我更喜欢提供所有必要的更简单实现的那个功能。

我个人发现,在没有实际经验和反馈的情况下担心未来的用例并没有多大用处。 KISS principle and the Unix philosophy 似乎没有试图创建解决所有未来问题的最终、最好的框架,而是产生了更好的结果。

正确的顺序是:

  1. 学习 C
  2. 变魔术

否则就不行了。 ({}) 不会为您改变语义。如果您的 add 需要 returns struct Super* 的函数,它将无法与 struct Sub 一起使用,即使您将缺少的 * 放在那里也是如此。

这只适用于 TutorialsPoint:

#include <stdio.h>
#include <stdlib.h>

int max(int a,int b){
    if(a>b)
        return a;
    return b;
}

struct Super{};

void add(struct Super *(*superRef)()) {
    struct Super *(*secretStorage)()=superRef;
    /* ... */
    struct Super *super = secretStorage();
    /* ... */
    free(super);
    printf("Stillalive\n");
}

int main()
{
    printf("Hello, World!\n");

    int (*myMax)(int,int); // <-- that is a function pointer

    myMax=max;             // <-- set with oldschool function
    printf("%d\n",myMax(1,2));

    myMax = ({             // <-- set with fancy magic
        int __fn__ (int x, int y) { return x < y ? x : y; }
        __fn__;
    });    
    printf("%d - intentionally wrong\n",myMax(1,2));

    add(
        ({
            struct Super* fn(){
                printf("Iamhere\n");
                return malloc(sizeof(struct Super));
            }
            fn;}));
    printf("Byfornow\n");
    return 0;
}

创建了一个小型库项目,其中嵌入了匿名魔法和堆分配中的匿名魔法。它没有多大意义,但它有效:

testlib.h

#ifndef TESTLIB_H_
#define TESTLIB_H_

struct Testruct{
    const char *message;
    void (*printmessage)(const char *message);
};

extern struct Testruct *(*nonsense())();

#endif

testlib.c

#include "testlib.h"
#include <stdio.h>
#include <stdlib.h>

const char *HELLO="Hello World\n";

struct Testruct *(*nonsense())(){
    return ({
        struct Testruct *magic(){
            struct Testruct *retval=malloc(sizeof(struct Testruct));
            retval->message=HELLO;
            retval->printmessage=({
                void magic(const char *message){
                    printf(message);
                }
                magic;
            });
            return retval;
        }
        magic;
    });
}

test.c

#include "testlib.h"
#include <stdio.h>
#include <stdlib.h>

int main(){
    struct Testruct *(*factory)()=nonsense();
    printf("Alive\n");
    struct Testruct *stuff=factory();
    printf("Alive\n");
    stuff->printmessage(stuff->message);
    printf("Alive\n");
    free(stuff);
    printf("Alive\n");
    return 0;
}

我按照 https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html 中的步骤构建了一个 运行 它(实际上是 3 个 gcc 调用:gcc -c -Wall -Werror -fpic testlib.cgcc -shared -o libtestlib.so testlib.ogcc -L. -Wall -o test test.c -ltestlib 等等与 LD_LIBRARY_PATH)

的战斗

第一个社区告诉我匿名函数不是 C 的一部分,因此替代建议是使用命名函数和指向它的指针。

其次,指向父结构的指针不能接收指向它的派生类型(嵌入式父结构)的指针,所以我不能在那里做太多事情。我尝试使用 void * 但也许存在使用内存地址然后访问结构的某些成员而不强制转换为特定类型的解决方案。我会在另一个问题中问这个问题。

我缺少的是以某种方式从覆盖的 运行 方法调用超级方法的能力?

src/super.h

struct Super {
    void (*run)();
};

struct Super *newSuper();

src/super.c

static void run() {
    printf("Running super struct\n");
}

struct Super *newSuper() {
    struct Super *super = malloc(sizeof(struct Super));
    super->run = run;
    return super;
}

src/Runner.h

struct Runner {

    void (*addFactoryMethod)(struct Super *(*ref)());

    void (*execute)();
};

struct Runner *newRunner();

src/runner.c

struct Super *(*superFactory)();

void addFactoryMethod(struct Super *(*ref)()) {
    superFactory = ref;
}

static void execute() {
    struct Super *sup = superFactory(); // calling cached factory method
    sup->run();
}

struct Runner *newRunner() {
    struct Runner *runner = malloc(sizeof(struct Runner));
    runner->addFactoryMethod = addFactoryMethod;
    runner->execute = execute;
    return runner;
}

test/runner_test.c

void anotherRunMethod() {
    printf("polymorphism working\n");
    // how can i've the ability to call the overridden super method in here?
}

struct Super *newAnotherSuper() {
    struct Super *super = malloc(sizeof(struct Super));
    super->run = anotherRunMethod;
    return super;
}

void testSuper() {
    struct Runner *runner = newRunner();
    runner->addFactoryMethod(&newAnotherSuper);
    runner->execute();
}

int main() {
    testSuper();
    return 0;
}

(引用你自己接受的答案)

Secondly a pointer to a parent struct can't receive a pointer to it's derived type (Embedded parent struct) so I can't do much there. I tried using void * but perhaps a solution might exists using memory address and then access some member of the struct without casting to specific types. I'll ask that in another question.

这是另一个指针,人们应该首先学习基础知识。你错过的东西叫做'forward declaration':

struct chicken; // here we tell the compiler that 'struct chicken' is a thing
struct egg{
  struct chicken *laidby; // while the compiler knows no details about 'struct chicken',
                          // its existence is enough to have pointers for it
};
struct chicken{           // and later it has to be declared properly
  struct egg *myeggs;
};

What I'm missing is the ability to call the super method from the overridden run method in some way?

这些不是方法,也没有覆盖。在您的代码中没有 OOP 发生,C 是一种过程编程语言。虽然有针对 C 的 OOP 扩展,但您真的不应该在不了解 C 基础知识的情况下使用它们。