Chinaunix

标题: 如何在C中执行动态生成的代码 [打印本页]

作者: shecx    时间: 2012-05-24 10:27
标题: 如何在C中执行动态生成的代码
本帖最后由 shecx 于 2012-05-24 10:28 编辑

我写了个C程序,假设为A.c,并且在执行的过程中,该程序会生成一个文件,假设为B.c,其中动态生成了某些函数,那么,我如何在A.c中执行B.c中的代码呢?当然,B.c中的函数名是确定好的。

有一个思路是这样,就是在A.c中先声明B.c中的函数(但不定义,因为定义在B.c中了),然后在需要的地方调用它。
然后,执行完A.c后,生成了B.c,再将两个文件A.c与B.c合并在一起编译链接成一个完整的程序。

但是上面的思路有个问题,就是在A.c中先声明B.c的函数,并在需要的地方调用它,链接器将无法链接,因为A.c的函数只有声明,却未有定义

请教高手有什么办法可以解决,或者其他方法也行,但我的代码却必须是通过这种方式动态生成的
作者: hellioncu    时间: 2012-05-24 10:32
生成的代码必须是C代码么?
限制了C,可以考虑下tcc,否则其他类型的脚本语言很多。
当然调用gcc之类的编译器也是一种办法,就是太重量级了
作者: aychxm    时间: 2012-05-24 10:34
换一种思路吧!
作者: shecx    时间: 2012-05-24 10:36
回复 3# aychxm


    没思路啊,关键是我的代码只能是动态生成的,很麻烦
作者: shecx    时间: 2012-05-24 10:38
回复 2# hellioncu


    不是编译器的问题啊,是我原来的A.c文件与B.c文件依赖关系导致的,我的B.c文件又必须是动态生成的
作者: hellioncu    时间: 2012-05-24 10:45
shecx 发表于 2012-05-24 10:38
回复 2# hellioncu


不知道是我没看明白你的问题还是你没看明白我的回复。
作者: shecx    时间: 2012-05-24 10:57
回复 6# hellioncu


    呵,麻烦说的详细一点,我的确有些不明白你的意思。

  不过我再说明一下我的意图:我的目的就是在A.c中能动态生成B.c,并且希望在A.c中能执行B.c中的代码。
  因此,我在我的A.c文件中加入动态生成B.c的代码,并且,我在A.c文件中也加入执行B.c代码中的函数的代码。但关键问题是,当我编译链接A.c时,就出错了,原因是链接A.c时,找不到B.c中的代码来链接,因为B.c是靠A.c生成的,而此时A.c未执行当然就未生成了。
  这就是我的问题所在。。。
  
作者: wwwsq    时间: 2012-05-24 11:01
在A.c中调用gcc把B.c编译成B.so,然后在A.c里面dlopen("B.so")、dlsym之类的,就可以调用B.c里面的函数了。

systemtap就是这么做的。这是成熟可靠的方法。

作者: cjaizss    时间: 2012-05-24 11:03
shecx 发表于 2012-05-24 10:57
回复 6# hellioncu

似乎你的思路不明确啊.
作者: shecx    时间: 2012-05-24 11:18
本帖最后由 shecx 于 2012-05-24 11:19 编辑

感谢各位的回复,不过我的问题似乎还是有些不明
假设我的A.c如下

// 动态生成B.c代码
void build() {
    File * pFile = fopen("B.c", "wt");
    fprintf("void other() { }", pFile);
    // 省略
}

// 声明B.c中的函数other,但定义在B.c中
void other();

void main() {
    build();  // 生成B.c与函数other()
   
    other(); // 执行B.c中的other(),问题在此,就是因为B.c还未生成,链接器将报错
}

当然,当我生成了B.c后,我还是需要将A.c与B.c合并链接的,但问题就是B.c都无法生成,避免不了链接器的检查

上面的问题我是说了个大概,其中省略了某些细节,请教大家有没有其他方法或思路
作者: zylthinking    时间: 2012-05-24 11:27
shecx 发表于 2012-05-24 11:18
感谢各位的回复,不过我的问题似乎还是有些不明
假设我的A.c如下


搜索一下弱符号, 看看能不能提供点思路
作者: tempname2    时间: 2012-05-24 11:29
动态链接不错啊。

void main (){
  void (*other)();
  build();
   handle   =   dlopen("b.so");
  other = dlsym(handle, "other");

other();
}
作者: lxsyd    时间: 2012-05-25 13:00
运行a.c的代码的时候,调用gcc生成动态库,然后load进来调用。
作者: dzmcs    时间: 2012-05-28 18:30
当然可以


int main()
{
(void (*p))();
build();
//生成动态库,大概这样:popen("gcc -fPIC -shared b.c -o libb.so ","r");
//dlopen动态库
//获取动态库的函数p = dlsym(... , other);
p();
}
作者: starwing83    时间: 2012-05-28 19:43
生成B的代码是必须在执行的时候做,还是允许在编译a的时候做?
作者: OwnWaterloo    时间: 2012-05-28 20:34
回复 15# starwing83

如果不改需求:
我写了个C程序,假设为A.c,并且在执行的过程中,该程序会生成一个文件,假设为B.c,其中动态生成了某些函数,那么,我如何在A.c中执行B.c中的代码呢?当然,B.c中的函数名是确定好的。

显然是运行时。

看吧,这功能肯定有市场
为了compile once, debug anywhere,带个虚拟机都可以被接受(说不定还带有JIT);那带个编译器又有神马不可以
作者: starwing83    时间: 2012-05-28 20:36
回复 16# OwnWaterloo


    可以带,我不反对

我反对的是带个gcc……
作者: OwnWaterloo    时间: 2012-05-28 20:38
回复 17# starwing83

反对带gcc是什么意思?

作者: starwing83    时间: 2012-05-28 20:40
回复 18# OwnWaterloo


    运行时带个gcc(还有6M的cc1.exe,还有数以百计的头文件,还有几十个.a、.o文件等等……)
作者: OwnWaterloo    时间: 2012-05-28 20:51
回复 19# starwing83

你是觉得它大?还是乱?还是仅仅感觉不舒服?
作者: starwing83    时间: 2012-05-28 22:55
回复 20# OwnWaterloo


    都有……tcc的话我就满意了……
作者: OwnWaterloo    时间: 2012-05-28 23:07
回复 21# starwing83

对不是"一整坨"的程序,你是不是不能接受?
记得以前也讨论过.out既作为app又作为lib vs .exe作为app + .dll作为lib ……


即使将tcc(且不说它产生的代码质量)作为库(gcc也许可以,即使它不行也可以找到其他C编译器)使用,动态链接真的与管道有什么区别么……
即使是静态链接,不可分割的一整块,它还是需要头文件……
难道要将头文件也嵌入到app里固定死?


本体只是个装饰,其实是零散一堆的软件又不是少数。
gcc就是,git也是,emacs/vim(记得有个text<->binary工具来着)还是。
只接受零散的binary文件,不接受零散的text文件?
gcc(以及许多以各种方式支持不同语言的软件)也有文本的零散的东西。 git里面记得有perl脚本。 emacs/vim就更不用提了。
不也用得尚好……

作者: starwing83    时间: 2012-05-28 23:12
回复 22# OwnWaterloo


    我自己的项目里面是不会的啦。

觉得不舒服。
作者: OwnWaterloo    时间: 2012-05-28 23:20
本帖最后由 OwnWaterloo 于 2012-05-28 23:20 编辑

回复 23# starwing83

嗯,最终又回到"洁癖" —— 对编程来说这通常应该是褒义词,因为其他某个贴里貌似被理解为贬义词了,你懂的所以提一下,你懂的…… —— 上去了……

而我个人感觉……  要么就vm only,没有native;要么就native only,要么就native+interpreter。
native+byte-code+interpret的语言实现都复杂到想吐……

而且值得么?这byte-code夹在中间不上不下的……

作者: sonicling    时间: 2012-05-28 23:44
我是在想:既然你能fprintf(fp, "void other() {}"); 为什么不把other直接写在代码里呢?

如果这个所谓的other是从不确定的地方输入进来的,恭喜你,你就是在做编译器,至少也得是个编译器的皮。
作者: starwing83    时间: 2012-05-28 23:47
回复 24# OwnWaterloo


    native也别带gcc啊,带clang好么亲~

那个,都要解释了,会没有byte-code么?
作者: fallening_cu    时间: 2012-05-28 23:47
上 lua 吧 .......
作者: OwnWaterloo    时间: 2012-05-28 23:52
本帖最后由 OwnWaterloo 于 2012-05-28 23:57 编辑

回复 26# starwing83

前面说的"text<->binary工具"搞错了。 我就说怎么总感觉不对劲……  应该是hex才更准确……
xxd(vim)与hexl(emacs)…… emacs带的utils还不止这一个……

感觉clang不够成熟……

我也想问interpreter与byte-code+vm之间有没有绝对界限…… 如果有,界限又在哪?

作者: starwing83    时间: 2012-05-29 00:02
回复 28# OwnWaterloo


    bc+vm是解释器的一种实现方式而已。

另一种(可能的)方式是直接通过遍历parser生成的语法树的形式来进行计算。

ruby就是这样的。
作者: OwnWaterloo    时间: 2012-05-29 00:05
回复 29# starwing83

那我们以前关于是否要有vm在争个啥{:3_188:}
作者: gtkmm    时间: 2012-05-29 00:11
wwwsq 发表于 2012-05-24 11:01
在A.c中调用gcc把B.c编译成B.so,然后在A.c里面dlopen("B.so")、dlsym之类的,就可以调用B.c里面的函数了。 ...



CUDA和OpenCL也是这么干的。。。

如果你想快一些,你可以使用clang。

因为有这样的问题:


#include XXXX
#include YYYY

//your code here。

你自己的代码没几行,但是因为你include别的东西,导致最后需要编译的文件很大,
clang可以先把include的处理成ast,之后你直接分析你新加的代码就是了。

作者: starwing83    时间: 2012-05-29 00:11
回复 30# OwnWaterloo


    vm是已知最有效的解释器实现方式。

ruby的效率和内存问题你也知道的。
作者: OwnWaterloo    时间: 2012-05-29 00:41
回复 32# starwing83

可能是以前讨论时候没说清楚吧……

比如说"gcc",就不是指将那套exe,dll,out,so,lib,a,h什么的直接搬来用。
也许中间会这么搞,但至少最终不是这么搞。
这么说吧"gcc"的意思这么说更准确:只做L->C的转换,C->native的转换就交给其他现有的工具/库去搞定。
也许是gcc,也许是gcc的后端,也许是clang/tcc,或者直接llvm。
也可以按你说的,只负责产生代码,用户自己去编译。

C->asm的工作也不是完全不做,但肯定不是最初的重点。
必须要有一个"保底"手段 —— 当对其他arch的asm不了解的时候,至少还有"gcc"(成熟的C->asm)的工具可用。



至于"interpreter"的意思也是:edit->compile->link->run->result的cycle太繁琐了,也太慢了。
native code是最终release/deliver(给他人)用。对于自己甚至可以始终用interpret。
而develop时的cycle要更短,至少是edit->run->result,甚至edit->result。

至于interpreter实现到什么程度并不是最开始的重点。但从设计vm开始就有过早优化的感觉了。这只是develop时、quick and dirty时用的东西。

甚至这东西我都想省略的,只支持native。运行时加载source就source->C->cc&ld->dll/so->GetProcAddress/dlsym……
只是想到bootstrapping可能需要这东西,作为一个副产品,就先凑合着用吧……

作者: starwing83    时间: 2012-05-29 00:45
本帖最后由 starwing83 于 2012-05-29 00:46 编辑

回复 33# OwnWaterloo


    这么说我就好接受多了……但是C功能有限,有很多东西做不出来啊(tail call、continuation神马的……)
作者: wwwsq    时间: 2012-05-29 01:01
gtkmm 发表于 2012-05-29 00:11
CUDA和OpenCL也是这么干的。。。

如果你想快一些,你可以使用clang。




gcc貌似也支持预编译了。

http://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

不知道速度有没有clang的快

作者: starwing83    时间: 2012-05-29 01:04
回复 35# wwwsq


    他说的其实不是预处理头文件…………这个gcc很早就有了……

他说的是,clang不仅仅是个编译器,还是一套工具库,你可以拿它将代码分析成ast存起来,然后附加上你加的代码编译出来的ast一起编译,这样速度快~~
作者: OwnWaterloo    时间: 2012-05-29 01:06
回复 34# starwing83

tail call嘛gcc肯定支持一些,这个是有保障的,因为haskell的关系……
总的来说C没有操作stack的能力……先用软件实现stack保底,然后对已知的arch用asm或者用llvm?

作者: starwing83    时间: 2012-05-29 01:07
回复 37# OwnWaterloo


    那你就需要vm罗……(不然你的stack怎么实现……)
作者: OwnWaterloo    时间: 2012-05-29 01:14
回复 38# starwing83

指令是native的,就一个虚拟的stack,称为vm我觉得太不准确了。。。
就像一些人将类似arena —— 多次分配整体释放apr_pool, obstack(glibc)  —— 的东西称为gc一样。。。
这么高调很容易让人误会。

作者: starwing83    时间: 2012-05-29 01:20
回复 39# OwnWaterloo


    ?C怎么搞这件事?好像很困难的样子……
作者: starwing83    时间: 2012-05-29 01:21
回复 39# OwnWaterloo


    顺便说一下,所谓dyncall也称自己那个汇编写的东西是vm,这个嘛……
作者: OwnWaterloo    时间: 2012-05-29 01:26
回复 40# starwing83

你说arena?一说接口就明白了。。。

arena_t* a = ...;
void* p0 = arena_acquire(a, size0, ...);
void* p1 = arena_acquire(a, size1, ...);
... 诸多acquire... 最终
arena_clear(a); // 上面分配的内存全部回收

但还就是有人将这称为gc……


回复 41# starwing83

同样,也就是有人认为控制了调用约定就控制了vm……


太高调了……

作者: starwing83    时间: 2012-05-29 01:28
回复 42# OwnWaterloo


    我当然知道arena是啥……Python源代码里面到处用的……

我是说用native做一个C本身能用的stack,太难了吧?
作者: OwnWaterloo    时间: 2012-05-29 01:37
回复 43# starwing83

>> 我是说用native做一个C本身能用的stack,太难了吧?

显然不是C本身能用的stack……
obstack(glibc)比arena稍微高级点,可以不整体释放,而是LIFO式的分配释放。
然后弄个全局的obstack(或者TSS的),而(需要操作stack的)函数都是无参数的,直接通过obstack分配/获取/释放。

写下上面的文字发现坏事了……  即使是无参的也可能会消耗C的stack……
难道只能CPS了么………………

作者: wwwsq    时间: 2012-05-29 03:02
本帖最后由 wwwsq 于 2012-05-29 03:02 编辑
starwing83 发表于 2012-05-29 01:04
回复 35# wwwsq



我对clang了解不多啊。

gcc可以把头文件预编译成pch文件,把cpp文件预编译成obj文件。这两者组合起来,不是也只要编译你自己的那些代码就可以了吗?

我不知道ast相比(pch+obj),速度更快体现在哪里?


ps:回头我去研究一下ast,貌似很有趣的样子。clang据说可以生成编译中间产物,提供给ide做代码自动提示、自动完成之类的,指的是这个ast吗?


作者: haitao    时间: 2012-05-29 10:52
可能要找 解释性c子集 的解释器了

delphi就有 delphi子集 解释器,我把它进一步改成连界面、事件都可以动态加载执行了:
http://211.162.123.246:443/httpdisk/haitaosoft/?app=delphiscript
作者: gtkmm    时间: 2012-05-29 12:55
wwwsq 发表于 2012-05-29 01:01
gcc貌似也支持预编译了。

http://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

不是一回事。
作者: 龙骨五钱    时间: 2012-05-31 15:46
相信我,宏足够实现这个功能了。
作者: JohnBull    时间: 2012-06-03 10:59
把b.c编译成动态库
作者: cobras    时间: 2012-06-03 11:10
用ch吧,c的解释器,超好用。
作者: 龙骨五钱    时间: 2012-06-03 12:04
把生成B的代码和使用B的代码分开,通过宏来控制编译哪一部分的代码。明白吧?要不你就先把用到的B的函数写一个空壳。
作者: 龙骨五钱    时间: 2012-06-03 12:08
刚刚说的不完整,第二种方法是说在A中写空函数体,然后也是通过宏(不嫌蛋疼可以通过自己修改自己的代码)把这些空壳去掉,再链接时就用的B的函数了。本质上跟第一种方法差不多。编译时可以定义宏嘛。
作者: starwing83    时间: 2012-06-03 14:34
回复 52# 龙骨五钱


    根本就用不着,直接不提供函数定义,链接的时候根据选择链接合适的B即可。
作者: cobras    时间: 2012-06-03 14:38
用cint或ch吧,专门为脚本而生的。
作者: 龙骨五钱    时间: 2012-06-03 15:26
本帖最后由 龙骨五钱 于 2012-06-03 15:27 编辑

回复 53# starwing83


   
shecx 发表于 2012-05-24 10:57
呵,麻烦说的详细一点,我的确有些不明白你的意思。

  不过我再说明一下我的意图:我的目的就是在A.c中能动态生成B.c,并且希望在A.c中能执行B.c中的代码。
  因此,我在我的A.c文件中加入动态生成B.c的代码,并且,我在A.c文件中也加入执行B.c代码中的函数的代码。但关键问题是,当我编译链接A.c时,就出错了,原因是链接A.c时,找不到B.c中的代码来链接,因为B.c是靠A.c生成的,而此时A.c未执行当然就未生成了。
  这就是我的问题所在。。。





欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2