匿名函数返回动态分配的值
编辑
简介:基本上是一个Runner,它接收指向多态工厂方法的指针,该方法被缓存并稍后调用实例化并在事件发生时执行。
为了使事情更容易传达,我创建了一个包含源文件和测试文件的小型隔离项目。 下载
运行make
进行编译
运行make test
以运行测试
希望这可以帮助。
问题是基于一种设计模式解决方案,在其他语言中很容易实现,但很难在C中实现。缩小的代码如下。
在链接的答案的基础上,我试图找到匿名函数中动态生成的值的解决方案。
摘录答案:
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 *(*)()
根据澄清请求,考虑接收对结构对象的引用(非实例化)的静态库文件中的接收函数。 lib从客户端代码接收此对象。
其次,客户端或静态库库不会立即实例化接收的结构引用。 稍后当系统中有通知时,将调用结构引用来实例化并执行其余的东西。
我再说一遍,具体要求是保存对库用户传递的结构的非实例化引用(客户端代码)。 希望这清楚。
正确的顺序是:
- 学习C
- 做魔术
它只是不会以其他方式工作。 ({})
不会为您弯曲语义。 如果你的add
需要一个返回struct Super*
的函数,它就不能用于struct Sub
,即使你把缺少的*
放在那里也是如此。
这只适用于TutorialsPoint :
#include #include 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 #include 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 #include 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.c
, gcc -shared -o libtestlib.so testlib.o
, gcc -L. -Wall -o test test.c -ltestlib
gcc -shared -o libtestlib.so testlib.o
gcc -L. -Wall -o test test.c -ltestlib
和LD_LIBRARY_PATH
有点争吵)
问题中显示的代码不是标准C,而是GCC支持的GNU C变体。 不幸的是,似乎没有gnu-c标签,正确指定涉及的C的变体。
此外,用例似乎依赖于将特定类型的面向对象范例转换为C库接口。 这太可怕了,因为它涉及C根本没有的假设和特征。 C(和GNU-C),C ++和Objective-C是不同的编程语言是有原因的。
“函数返回动态赋值”的简单答案,其中值的类型对库是不透明的,是使用void *
,对于函数指针,使用(void *)()
。 请注意,在POSIX C中, void *
也可以包含函数指针。
更复杂的答案将描述像GObject这样的库如何在C语言中支持面向对象的范例。
在实践中,特别是在POSIX C中,使用类型标记(通常是int
,但可以是任何其他类型)和union,可以实现多态结构,基于结构的并集,所有结构都具有相同的第一个元素的类型标记。 这种function最常见的例子是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.3p6)。 根本没有必要使用union,它足以定义和显示union。
尽管如此,为了便于维护(并且为了避免语言律师想要在C标准中没有阅读该段落的论点),我建议使用包含匿名联合的结构作为库接口中的“基础”类型。
例如,库可能提供一个函数来返回某个对象的实际大小:
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正在尝试实现工厂模式 ,其中库函数为创建的对象提供规范(OOP中的类 ),以及稍后生成这些对象的方法。
C中实现动态类型的唯一方法是通过我在上面显示的那种多态性。 这意味着未来对象的规范(同样,OOP中的类 )必须是普通对象本身。
工厂模式本身很容易在标准C中实现。库头文件包含例如
#include /* * 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()
退出(丢弃/释放)工厂本身。 除了额外的错误检查, factory_produce(factory)
相当于(factory)->produce(factory)
, factory_retire(factory)
到(factory)->retire(factory)
。
mem_factory(ptr, len)
函数是库提供的工厂函数的示例。 它创建了一个工厂,它生成mem_factory()
调用时看到的动态分配的数据副本。
图书馆实施本身就是这样的
#include #include #include 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 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 #include #include #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 / O FILE
结构通常包含函数指针,而fopen()
, fread()
, fwrite()
等函数只是这些函数的包装器。 如果C库支持类似于GNU fopencookie()
的接口,情况尤其如此。
POSIX.1套接字,特别是struct sockaddr
类型,是本答案中首先显示的多态结构的原始原型。 因为它们的接口不支持类似于fopencookie()
任何东西(即,重写例如send()
, recv()
, read()
, write()
, close()
)的实现,所以不需要函数指针。
因此,请不要问哪一个更合适,因为两者都是非常常用的,并且它在很大程度上取决于细节。一般来说,我更喜欢能够提供更简单实现的那个,提供所有必要的function。
我个人发现,在没有实际经验和反馈的情况下担心未来的用例并没有用。 而不是试图创建解决所有未来问题的最终所有,最好的框架, KISS原则和Unix哲学似乎产生了更好的结果。
经过多次努力,这是解决方案,但感谢社区的帮助。
第一个社区告诉我匿名函数不是C的一部分,所以另一个建议是使用命名函数和指向它的指针。
其次,指向父结构的指针不能接收指向它的派生类型(嵌入式父结构)的指针,所以我在那里做不了多少。 我尝试使用void *
但是可能使用内存地址存在一个解决方案,然后访问结构的某个成员而不转换为特定类型。 我会问另一个问题。
我缺少的是能够以某种方式从重写的run方法调用super方法吗?
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; }
测试/ 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; }
(引用自己接受的答案)
其次,指向父结构的指针不能接收指向它的派生类型(嵌入式父结构)的指针,所以我在那里做不了多少。 我尝试使用void *但是可能使用内存地址存在一个解决方案,然后访问结构的某个成员而不转换为特定类型。 我会问另一个问题。
这是另一个指针 ,人们应该首先学习基础知识。 你想念的东西叫做“前瞻宣言”:
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; };
我缺少的是能够以某种方式从重写的run方法调用super方法吗?
这些不是方法,也没有覆盖。 在您的代码中没有OOP发生,C是一种过程编程语言。 虽然有C语言的OOP扩展,但你真的不应该在不了解C基础知识的情况下使用它们。