简体   繁体   English

匿名函数返回动态分配的值

[英]Anonymous functions return dynamically allocated values

The question is based on a design pattern solution easily doable in other languages but difficult to implement in C. The narrowed down code is below.该问题基于在其他语言中很容易实现但在 C 中难以实现的设计模式解决方案。缩小范围的代码如下。

Building on this answer , I'm trying to find a solution for the dynamically generated values in an anonymous function.基于此答案,我正在尝试为匿名函数中动态生成的值找到解决方案。

Excerpt from the answer:摘自答案:

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

Static Library Code静态库代码

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.
}

Client Code linked: User of the Library Code客户端代码链接:图书馆代码的用户

struct Sub{
     struct Super *super;
}

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

Error:错误:

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

As per the request for clarification, think of the receiving function in a static library file receiving references to the structure objects (non-instantiated).根据澄清请求,请考虑静态库文件中的接收函数,该函数接收对结构对象的引用(未实例化)。 The lib receives this object from the client code. lib 从客户端代码接收此对象。

Secondly the client or static library library doesn't instantiate the received structure reference right away.其次,客户端或静态库库不会立即实例化接收到的结构引用。 Later when there's a notification in the system, the structure reference is called to instantiate and execute the rest of the stuff.稍后当系统中有通知时,会调用结构引用来实例化并执行其余的内容。

I repeat, the specific requirement is to hold non-instantiated references to the structures passed by users of the library (client code).我再说一遍,具体要求是保存对库用户(客户端代码)传递的结构的非实例化引用。

Summary总结

Basically a Runner that receives pointer to a polymorphic factory method which it caches and later calls to instantiate and executes when an event occurs.基本上是一个 Runner,它接收指向多态工厂方法的指针,该方法缓存并稍后调用以在事件发生时实例化并执行。

The correct order is:正确的顺序是:

  1. learn C学C
  2. do magic做魔术

It just will not work in the other way.它只是不会以其他方式工作。 ({}) does not bend the semantics for you. ({})不会为您改变语义。 If your add expects a function which returns struct Super* , it will not work with struct Sub , not even if you put the missing * there.如果您的add需要一个返回struct Super*的函数,则它不会与struct Sub ,即使您将丢失的*放在那里也不会。

This just works on TutorialsPoint :这仅适用于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;
}

Created a small library project with anonymous magic embedded in anonymous magic and heap allocation.创建了一个小型库项目,其中嵌入了匿名魔法和堆分配中的匿名魔法。 It does not make much sense, but it works:它没有多大意义,但它有效:

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测试.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;
}

I followed the steps in https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html for building an running it (practically 3 gcc calls: gcc -c -Wall -Werror -fpic testlib.c , gcc -shared -o libtestlib.so testlib.o , gcc -L. -Wall -o test test.c -ltestlib and a bit of fight with LD_LIBRARY_PATH )我按照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的一些斗争)

The code shown in the question is not standard C, but the GNU C variant that GCC supports.问题中显示的代码不是标准 C,而是 GCC 支持的 GNU C 变体。 Unfortunately, there does not seem to be a tag, to correctly specify the variant of C involved.不幸的是,似乎没有标签来正确指定所涉及的 C 变体。

Furthermore, the use case seems to rely on shoehorning specific type of object-oriented paradigm into a C library interface.此外,用例似乎依赖于将特定类型的面向对象范式硬塞进 C 库接口中。 This is horrible, because it involves assumptions and features C simply does not have.这太可怕了,因为它涉及 C 根本没有的假设和特征。 There is a reason why C (and GNU-C) and C++ and Objective-C are different programming languages. C(和 GNU-C)、C++ 和 Objective-C 是不同的编程语言是有原因的。

The simple answer to "functions returning dynamically allocated values" where the type of the value is opaque to the library, is to use void * , and for function pointers, (void *)() .“函数返回动态分配的值”的简单答案,其中的类型对库不透明,是使用void * ,对于函数指针, (void *)() Note that in POSIX C, void * can also hold a function pointer.请注意,在 POSIX C 中, void *也可以保存函数指针。

The more complex answer would describe how libraries like GObject support object-oriented paradigms in C.更复杂的答案将描述像GObject这样的库如何支持 C 中的面向对象范式。

In practice, especially in POSIX C, using a type tag (usually int , but can be any other type) and an union, one can implement polymorphic structures, based on an union of structures with all having that type tag as the same first element.在实践中,尤其是在 POSIX C 中,使用类型标记(通常是int ,但可以是任何其他类型)和联合,可以实现多态结构,基于结构的联合,所有具有该类型标记的结构都作为相同的第一个元素. The most common example of such functionality is struct sockaddr .这种功能最常见的例子是struct sockaddr

Basically, your header file defines one or more structures with the same initial member, for example基本上,您的头文件定义了一个或多个具有相同初始成员的结构,例如

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)();
};

and at the end, an union type, or a structure type with an anonymous union (as provided by C11 or GNU-C), of all the structure types,最后,所有结构类型的联合类型或具有匿名联合的结构类型(由 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;
    };
};

Note that technically, wherever that union is visible, it is valid to cast any pointer of any of those structure types to another of those structure types, and access the type member (see C11 6.5.2.3p6).请注意,从技术上讲,只要该联合可见,将任何这些结构类型的任何指针强制转换为这些结构类型中的另一个,并访问type成员(请参阅 C11 6.5.2.3p6)都是有效的。 It is not necessary to use the union at all, it suffices for the union to be defined and visible.根本没有必要使用联合,只要联合被定义和可见就足够了。

Still, for ease of maintenance (and to avoid arguments with language lawyer wannabes who did not read that paragraph in the C standard), I do recommend using the structure containing the anonymous union as the "base" type in the library interface.尽管如此,为了便于维护(并避免与没有阅读 C 标准中该段落的语言律师崇拜者争论),我建议使用包含匿名联合的结构作为库接口中的“基本”类型。

For example, the library might provide a function to return the actual size of some object:例如,库可能提供一个函数来返回某个对象的实际大小:

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;
}

It seems to me OP is trying to implement a factory pattern , where the library function provides the specification ( class in OOP) for the object created, and a method to produce those objects later.在我看来,OP 正在尝试实现工厂模式,其中库函数为创建的对象提供规范(OOP 中的),以及稍后生成这些对象的方法。

The only way in C to implement dynamic typing is via the kind of polymorphism I show above.在 C 中实现动态类型的唯一方法是通过我上面展示的那种多态性。 This means that the specification for the future objects (again, class in OOP) must be an ordinary object itself.这意味着未来对象(同样是 OOP 中的)的规范必须是一个普通对象本身。

The factory pattern itself is pretty easy to implement in standard C. The library header file contains for example工厂模式本身在标准 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);

where factory_produce() is a helper function which invokes the factory to produce one object, and factory_retire() retires (discards/frees) the factory itself.其中factory_produce()是一个辅助函数,它调用工厂来生产一个对象, factory_retire()退出(丢弃/释放)工厂本身。 Aside from the extra error checking, factory_produce(factory) is equivalent to (factory)->produce(factory) , and factory_retire(factory) to (factory)->retire(factory) .除了额外的错误检查, factory_produce(factory)相当于(factory)->produce(factory) ,和factory_retire(factory)(factory)->retire(factory)

The mem_factory(ptr, len) function is an example of a factory function provided by a library. mem_factory(ptr, len)函数是库提供的工厂函数的示例。 It creates a factory, that produces dynamically allocated copies of the data seen at the time of the mem_factory() call.它创建一个工厂,生成在mem_factory()调用时看到的数据的动态分配副本。

The library implementation itself would be something along the lines of库实现本身将类似于

#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;
}

Essentially, the struct any_factory type is actually polymorphic (not in the application, but within the library only).本质上, struct any_factory类型实际上是多态的(不是在应用程序中,而是仅在库中)。 All its variants ( struct mem_factory here) has the two initial function pointers in common.它的所有变体(这里是struct mem_factory )都有两个共同的初始函数指针。

Now, if we examine the code above, and consider the factory pattern, you should realize that the function pointers provide very little of value: you could just use the polymorphic type I showed earlier in this answer, and have the inline producer and consumer functions call subtype-specific internal functions based on the type of the factory.现在,如果我们检查上面的代码,并考虑工厂模式,您应该意识到函数指针提供的价值非常小:您可以只使用我在本答案前面展示的多态类型,并具有内联生产者和消费者函数根据工厂的类型调用特定于子类型的内部函数。 factory.h :工厂.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 */

and factory.c :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;
}

If we look at standard C and POSIX C library implementations, we'll see that both of these approaches are used.如果我们查看标准 C 和 POSIX C 库实现,我们会看到这两种方法都被使用。

The standard I/O FILE structure often contains function pointers, and the fopen() , fread() , fwrite() , etc. functions are just wrappers around these.标准的 I/O FILE结构通常包含函数指针,而fopen()fread()fwrite()等函数只是这些的包装器。 This is especially the case if the C library supports an interface similar to GNU fopencookie() .如果 C 库支持类似于 GNU fopencookie()的接口,则尤其如此。

POSIX.1 socket, especially the struct sockaddr type, is the original prototype for the polymorphic structure shown first in this answer. POSIX.1 套接字,尤其是struct sockaddr类型,是本答案中首先显示的多态结构的原始原型。 Because their interface does not support anything similar to fopencookie() (that is, overriding the implementation of eg send() , recv() , read() , write() , close() ), there is no need for the function pointers.因为它们的接口不支持类似于fopencookie()任何东西(即覆盖了例如send()recv()read()write()close() ),所以不需要函数指针.

So, please do not ask which one is more suitable, as both are very commonly used, and it very much depends on minute details.. In general, I prefer the one that yields a simpler implementation providing all the necessary functionality.所以,请不要问哪个更合适,因为两者都非常常用,而且很大程度上取决于细节。一般来说,我更喜欢能够提供所有必要功能的更简单的实现。

I have personally found that it is not that useful to worry about future use cases without practical experience and feedback first.我个人发现,在没有实践经验和反馈的情况下担心未来的用例是没有用的。 Rather than trying to create the end-all, best-ever framework that solves all future problems, the KISS principle and the Unix philosophy seem to yield much better results. KISS 原则Unix 哲学似乎产生了更好的结果,而不是试图创建解决所有未来问题的终极、有史以来最好的框架。

(Quoting your accepted answer to yourself) (引用你自己接受的答案)

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.我尝试使用 void * 但也许使用内存地址可能存在解决方案,然后访问结构的某些成员而不转换为特定类型。 I'll ask that in another question.我会在另一个问题中问这个。

This is yet another pointer that one should learn the basics first.这是人们应该首先学习基础知识的另一个指针 The thing you miss is called '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?我缺少的是能够以某种方式从重写的 run 方法调用 super 方法的能力?

These are not methods and there is no override.这些不是方法,也没有覆盖。 In your code no OOP happens, C is a procedural programming language.在您的代码中没有发生 OOP,C 是一种过程编程语言。 While there are OOP extensions for C, you really should not go for them without knowing C basics.虽然有 C 语言的 OOP 扩展,但您真的不应该在不了解 C 基础知识的情况下使用它们。

First community told me that anonymous functions are not part of C, so the alternate suggestion is to use named functions and pointer to it.第一个社区告诉我匿名函数不是 C 的一部分,所以替代建议是使用命名函数和指向它的指针。

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.我尝试使用void *但也许使用内存地址可能存在解决方案,然后访问结构的某些成员而不转换为特定类型。 I'll ask that in another question.我会在另一个问题中问这个。

What I'm missing is the ability to call the super method from the overridden run method in some way?我缺少的是能够以某种方式从重写的 run 方法调用 super 方法的能力?

src/super.h源代码/超级.h

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

struct Super *newSuper();

src/super.c源代码/超级.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源代码/跑步者.h

struct Runner {

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

    void (*execute)();
};

struct Runner *newRunner();

src/runner.c 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测试/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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM