免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 24493 | 回复: 63
打印 上一主题 下一主题

C语言对闭包运算的支持 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-11-30 14:24 |只看该作者 |倒序浏览
今天看lua也支持闭包,忽然想起C语言是不是通过某种方式也可以支持闭包呢,于是做了如下测试
  1. //A program for test "closure" in C
  2. //warning or error in ISO C PEDANTIC
  3. //davelv 2011-11-30
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #define SIZE_I 10
  7. #define SIZE_J 10

  8. struct s_t
  9. {
  10.         int        data[SIZE_J];
  11. };
  12. //output
  13. void print(struct s_t *s, int sizei, int sizej)
  14. {
  15.         int i,j;
  16.         for(i=0; i<sizei; i++)
  17.         {
  18.                 for(j=0; j<sizej; j++)
  19.                         printf("\t%d",s[i].data[j]);
  20.                 puts("");
  21.         }
  22. }
  23. int main(void)
  24. {

  25.         struct s_t st[SIZE_I];
  26.         int i , j ;
  27.         //filling st with random numbers
  28.         for (i = 0; i < SIZE_I; i++)
  29.         {
  30.                 for(j = 0; j < SIZE_J; j++)
  31.                         st[i].data[j]=rand()%1000;
  32.         }
  33.         //"CLOSURE"func compare used by qsort
  34.         int compare(const void *a, const void *b)
  35.         {
  36.                 return ((const struct s_t*)a)->data[i] - ((const struct s_t*)b)->data[i];
  37.         };
  38.         //test and output
  39.         for (i = 0; i<SIZE_I ;i++)
  40.         {
  41.                 printf("%s %d\n","Sort by index", i);
  42.                 qsort(st, 10, sizeof(struct s_t), compare);
  43.                 print(st, SIZE_I, SIZE_J);
  44.         }

  45.         return 0;
  46. }
复制代码
Linux X64_86下GCC4.6.2编译时发现
使用 -Wall -std=c99 时没有任何警告错误
使用 -Wall -std=c99 -pedantic时会警告ISO禁止函数嵌套,但是可以通过编译

最后执行程序结果表明这种C“方言”可以支持C++/LISP 等多范式/函式 语言所支持的闭包运算。只不过形式稍稍不同,必须先定义完毕后才能使用。

欢迎大家提出新的看法和意见。
PS:这个点子是老谭启发我的,因为第一次在函数里面定义或者声明另外一个函数的使用方式是在他书上看到的。

论坛徽章:
14
巨蟹座
日期:2013-11-19 14:09:4615-16赛季CBA联赛之青岛
日期:2016-07-05 12:36:0515-16赛季CBA联赛之广东
日期:2016-06-29 11:45:542015亚冠之全北现代
日期:2015-07-22 08:09:472015年辞旧岁徽章
日期:2015-03-03 16:54:15巨蟹座
日期:2014-12-29 08:22:29射手座
日期:2014-12-05 08:20:39狮子座
日期:2014-11-05 12:33:52寅虎
日期:2014-08-13 09:01:31巳蛇
日期:2014-06-16 16:29:52技术图书徽章
日期:2014-04-15 08:44:01天蝎座
日期:2014-03-11 13:06:45
2 [报告]
发表于 2011-11-30 15:13 |只看该作者
不知道什么叫闭包,但C/C++不支持 子函数
C++可以用 function object (古名 functor) 来替代,C好像没办法

论坛徽章:
0
3 [报告]
发表于 2011-11-30 16:14 |只看该作者
回复 2# bruceteen

ISO C是不支持函数嵌套的。
不过C++11 引入lambda运算,这个就是闭包,例如如下代码
  1. void KillByVal(const int x , std::vector<int> & Vec) {
  2. std::remove_if(v.begin(), v.end(), bool (int& n) { n == x; }
  3. };
复制代码

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
4 [报告]
发表于 2011-11-30 16:59 |只看该作者
不知道什么叫闭包,但C/C++不支持 子函数
C++可以用 function object (古名 functor) 来替代,C好像没办法
bruceteen 发表于 2011-11-30 15:13



    如果是函数嵌套定义,觉得没必要。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
5 [报告]
发表于 2011-11-30 18:15 |只看该作者
本帖最后由 OwnWaterloo 于 2011-11-30 18:16 编辑

回复 1# davelv

原理大致是这样: http://bbs.chinaunix.net/viewthr ... 877#postnum12149775
运行时构造一小段机器代码, 传递额外参数并跳转到实际函数。

gcc,很明显,是在栈上产生这段机器代码。 这样就不用处理内存分配的问题。 当然也限制closure不能在containing函数退出之后使用。
但这种技巧原理很简单, 内存分配虽然不起眼但却是真正耗费精力的地方……
因为这段机器代码是需要被执行的, 需要执行权限……

如果直接使用mprotect/VirtualProtect来改变栈上权限, 相当于把DEP关掉了。
如果分配, mmap/VirtualAlloc 都是整页整页的来, 得自己拆分了用。
HeapCreate相关函数又是windows only的……

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
6 [报告]
发表于 2011-11-30 18:19 |只看该作者
回复 1# davelv

其实我很好奇为什么函数定义后会加一个分号呢……

  1. int compare(const void *a, const void *b)
  2. {
  3.                 return ((const struct s_t*)a)->data[i] - ((const struct s_t*)b)->data[i];
  4. };

  5. void KillByVal(const int x , std::vector<int> & Vec) {
  6. std::remove_if(v.begin(), v.end(), bool (int& n) { n == x; }
  7. };
复制代码
我见过会这样写代码的人当中,你不是第一个……

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
7 [报告]
发表于 2011-11-30 18:32 |只看该作者
回顾那帖发现居然以前和zylthinking吵了一架

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
8 [报告]
发表于 2011-11-30 18:46 |只看该作者
回顾那帖发现居然以前和zylthinking吵了一架
OwnWaterloo 发表于 2011-11-30 18:32


那一架其实是我赢了。。。。后来找场子, 你不理我了。。。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
9 [报告]
发表于 2011-11-30 19:19 |只看该作者
本帖最后由 OwnWaterloo 于 2011-11-30 19:29 编辑

回复 8# zylthinking

不搜出那帖这事我都忘了……  过程也不记得了……
输赢什么的是小事。 只要弄清楚这东西是干什么的, 什么场合该用明白人自然清楚。
反正我是没敢在任何实际代码中用这东西, 即使opencv真的搞的人抓耳挠腮也没敢用……


问题是这样的, 有些legacy code对回调的函数的签名设计不好, 例子很多: qsort/bsearch, signal_handler(libc), WndProc(Windows GUI), opencv也有。
f 将 callback 传递给 g, 之后 callback 可能在某些时刻被调用。
当它被调用的时候, 能够获取的信息包括它在某个不知道的地方被调用时传递的参数, 或者某些全局的东西, 静态变量/线程局部变量等……

一般来说, 完备 的签名是 return_type callback(void* context, params...) 。 g( ... , callback, void* context );
如果某个 Acallback 需要 params... 之外的信息, 就可以让f给g传递一个context参数, Acallback被调用时获取。
如果某个 Bcallback 不需要, 那就让它空着。

一个void* 参数就够了, 因为可以塞个什么结构体的地址进去……
但完全没有这个参数就是不完备的。 如果某个Ccallback 需要这样的信息时, 就很麻烦了……

上面说的是一般情况。
也存在一些案例, void* context 参数是隐式的。 如果需要可以通过另一个参数传递。
也存在一些案例, 没有这样的参数,无论是隐式或显式, 但需要时也能解决问题。

比如(那个原帖我忘是啥了……) 就说这个帖, 给qsort传递。  因为对qsort返回后肯定不会再使用cmp, 所以直接一个静态变量(或者多线程下线程局部)就够用了。
这就是没有context参数也能解决问题的案例。

但有些时候就不行, 比如opencv, 只能将callback当作一个通知, 然后遍历全部窗口让它们更新 —— 但其实只有一种一个是真正需要更新。
它就属于 g(callback, args...) callback调用时期不确定的那一类。


而我想做的是一种一般化(当然目前是限定在i386下)的解法。 无论具体问题是什么, 只要是这一类问题就可以使用。
这种机制叫parital application。

一些社区将函数调用(invoke/call) 叫做应用(apply) , apply(f, a, b c) 以 a b c 3个参数调用f 。
部分应用就是只给出一部分参数,  g = partial_apply(f, a) 。
得到的g是另一个函数, 可以继续部分应用或者全部应用 apply(g, b, c) 等效于 apply(f, a, b, c) 。
多个被部分应用的结果函数相互之间不会有干扰, 也可以同时存在。

如果在C里面实现了partial application的机制, 对这种缺少参数的问题只要先将那个缺少的参数部分应用, 并将返回的函数传递给需要callback的地方即可。
当callback真正被调用时会提供余下的参数。

嗯, 这些也只是理论上的搞搞。 实践上没这个胆子用……

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
10 [报告]
发表于 2011-11-30 19:38 |只看该作者
回复  zylthinking

不搜出那帖这事我都忘了……  过程也不记得了……
输赢什么的是小事。 只要弄清楚这 ...
OwnWaterloo 发表于 2011-11-30 19:19


赢是指我在明白你那段机器码之前估计出了它是在做什么, 并非技术上赢了, 不过引起对骂的原因本来就是我说搞汇编玩花招没太大意思, 因此切以为如果没猜错思路, 应该是我赢了的。
结果是你仗着那是机器码我不查 op cpde 就看不懂, 硬是逼着我认输。。。。。。。后来我反回去查了查, 感觉冤枉大了。。。。
不过后来我是用了一个类似的, 你那个是cdecl的, 我改把了改吧, 针对 stdcall 弄了一个, 还给发布了, 结果一堆鸟杀毒软件报病毒。。。。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP