有可能的。在软件渲染和图形等某些领域,动态代码生成甚至是主流。你发现在各种脚本语言中都有很多用处,在机器码中字节码的动态编译(.NET、Java,据我所知 Perl。最近 JavaScript 也加入了俱乐部)。
您还发现它也用于数学非常繁重的应用程序中,例如,如果您打算进行数千次这样的乘法运算,则从矩阵乘法中删除所有为零的乘法会有所不同。
我强烈建议您阅读代码的 SSA 表示。这是一种表示,其中每个原语都转换为所谓的三操作数形式,并且每个变量只分配一次(因此是相同的静态单一赋值形式)。
您可以对此类代码进行高阶优化,并且可以直接将该代码转换为可执行代码。不过,您不会在周末编写代码生成后端...
要了解 SSA 的外观,您可以尝试 LLVM 编译器。在他们的网站上,他们有一个小的“试用”小部件可以玩。您将 C 代码粘贴到一个窗口中,您会得到一些接近 SSA 表单的内容。
小例子它的样子:
让我们在 C 中使用这个整数平方根算法。(任意示例,我只是举了一些简单但不平凡的东西):
unsigned int isqrt32 (unsigned int value)
{
unsigned int g = 0;
unsigned int bshift = 15;
unsigned int b = 1<<bshift;
do {
unsigned int temp = (g+g+b)<<bshift;
if (value >= temp) {
g += b;
value -= temp;
}
b>>=1;
} while (bshift--);
return g;
}
LLVM 把它变成:
define i32 @isqrt32(i32 %value) nounwind {
entry:
br label %bb
bb: ; preds = %bb, %entry
%indvar = phi i32 [ 0, %entry ], [ %indvar.next, %bb ]
%b.0 = phi i32 [ 32768, %entry ], [ %tmp23, %bb ]
%g.1 = phi i32 [ 0, %entry ], [ %g.0, %bb ]
%value_addr.1 = phi i32 [ %value, %entry ], [ %value_addr.0, %bb ]
%bshift.0 = sub i32 15, %indvar
%tmp5 = shl i32 %g.1, 1
%tmp7 = add i32 %tmp5, %b.0
%tmp9 = shl i32 %tmp7, %bshift.0
%tmp12 = icmp ult i32 %value_addr.1, %tmp9
%tmp17 = select i1 %tmp12, i32 0, i32 %b.0
%g.0 = add i32 %tmp17, %g.1
%tmp20 = select i1 %tmp12, i32 0, i32 %tmp9
%value_addr.0 = sub i32 %value_addr.1, %tmp20
%tmp23 = lshr i32 %b.0, 1
%indvar.next = add i32 %indvar, 1
%exitcond = icmp eq i32 %indvar.next, 16
br i1 %exitcond, label %bb30, label %bb
bb30: ; preds = %bb
ret i32 %g.0
}
我知道一开始看起来很可怕。它甚至不是纯粹的 SSA-Form。您对该表示的阅读越多,它就越有意义。而且您还会发现为什么这种表示法如今被如此广泛地使用。
将您需要的所有信息封装到数据结构中很容易。最后,您必须决定是否要使用枚举或字符串作为操作码名称等。
顺便说一句 - 我知道我没有给你数据结构,而是更正式但实用的语言以及进一步研究的建议。
这是一个非常好的和有趣的研究领域。
编辑:在我忘记之前:不要忽视 .NET 和 Java 的内置功能。这些语言允许您从程序内部的字节码或源代码编译并执行结果。
干杯,尼尔斯
关于您的编辑:如何使用代码执行二进制 blob:
跳转到二进制 blob 取决于操作系统和平台。简而言之,您使指令缓存无效,也许您必须写回数据缓存,并且您可能必须在您已将代码写入的内存区域上启用执行权限。
在 win32 上它相对容易,因为如果将代码放在堆上,指令缓存刷新似乎就足够了。
您可以使用此存根开始:
typedef void (* voidfunc) (void);
void * generate_code (void)
{
// reserve some space
unsigned char * buffer = (unsigned char *) malloc (1024);
// write a single RET-instruction
buffer[0] = 0xc3;
return buffer;
}
int main (int argc, char **args)
{
// generate some code:
voidfunc func = (voidfunc) generate_code();
// flush instruction cache:
FlushInstructionCache(GetCurrentProcess(), func, 1024);
// execute the code (it does nothing atm)
func();
// free memory and exit.
free (func);
}