C标准允许自修改代码吗?

5

在C语言中,是否有一种可移植的方式实现自修改代码?

我问这个问题的原因是,从某种程度上说,面向对象编程依赖于自修改代码(因为在运行时执行的代码实际上是作为数据生成的,例如在虚表中),但是,如果这种方式被过度使用,它将阻止大多数编译器优化。

例如:

void add(char *restrict p, char *restrict pAddend, int len)
{
    for (int i = 0; i < len; i++)
        p[i] += *pAddend;
}

优化编译器可以将 *pAddend 提升出循环,因为它不会干扰 p。然而,在自修改代码中,这不再是一个有效的优化。

因此,似乎 C 不允许自修改代码,但同时,这是否意味着你不能在 C 中做一些像面向对象编程(OOP)的事情?C 真的支持自修改代码吗?


2
C语言具有函数指针,这就足以构建运行时调度机制。您不需要使用“自修改代码”。 - Nemo
我不会说在C++中的面向对象实现使用了自修改代码,我认为它只是数据驱动的代码。虚函数在概念上与简单的“switch”并没有什么本质区别。 - Vlad
1
@Mehrdad:通常,“自修改代码”是指更改实际的机器指令,但这实际上是一个定义问题。例如,您提供的优化示例在存在函数指针的情况下确实是有效的。那么,您所说的“自修改代码”究竟是什么意思? - Nemo
@Steve:我有点困惑你想说什么,抱歉...你能详细说明一下吗? - user541686
1
@Mehrdad:你说的话有矛盾之处。“我的例子在自修改代码上出了问题”+“函数指针是一种自修改代码”的说法=你并不知道自修改代码的含义。 - Nemo
显示剩余8条评论
2个回答

8
自修改代码在C语言中是不可能的,原因有很多,其中最重要的是:
  1. 编译器生成的代码完全由编译器决定,可能与试图编写自修改代码的程序员期望的代码完全不同。这是做SMC的一个根本性问题,而不仅仅是可移植性问题。
  2. C语言中的函数指针和数据指针是完全独立的;语言没有提供任何方法来相互转换。这个问题并不是根本性的,因为一些实现或更高级别的标准(如POSIX)保证了代码和数据指针共享表示。

除此之外,自修改代码只是一个非常糟糕的想法。20年前它可能有一些用途,但现在它只会导致错误、极差的性能和可移植性失败。请注意,在某些ISA上,指令缓存是否看到对缓存代码所做的更改甚至可能是未指定/不可预测的!

最后,虚函数表与自修改代码无关。这纯粹是修改函数指针,它们是数据,而不是代码。


1
+1 你的最后一句话很关键。由于某种原因,我认为像jmp EAX这样的间接指令在EAX更改时会修改自身...思考上的愚蠢错误。感谢你的答案。 - user541686
1
这不是真的。请查看POSIX和WinAPI上的页面保护机制。除了在iOS内核中进行代码签名等操作外,没有任何东西可以阻止您在运行时生成机器代码,将页面保护标志设置为EXEC并使用C风格函数指针将控制传递给它。 - Sergey K.

3

严格来说,如果我正确理解标准的话,在C或C++中无法以可移植的方式实现自修改代码。

C/C++中的自修改代码指的是以下内容:

uint8_t code_buffer[FUNCTION_SIZE];
void call_function(void)
{
   ... modify code_buffer here to the machine code we'd like to run.
   ((void (*)(void))code_buffer)();
}

这种做法是不合法的,在大多数现代架构上会导致崩溃。在哈佛结构中,可执行代码是严格只读的,因此它不能成为任何标准的一部分。

大多数现代操作系统都有一种能力来实现这种黑客技术,其中之一就是动态重新编译器所使用的mprotect()函数,例如Unix系统。


1
自修改代码与编写“新鲜”代码并执行它的代码又是完全不同的事情。正如Mehrdad所观察到的那样,如果您不知道首先如何生成/优化该代码,那么修改编译器生成的代码就非常困难,因为机器指令不一定与AST有任何特别明显的关系。 - Steve Jessop
虽然你不能编写“自修改的C代码”,但C编译器可以发出“自修改的机器码”。我认为这个问题允许两种情况,我不知道原帖作者是否考虑了这两种情况,也不知道是否有任何C编译器这样做过。 - hippietrail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接