C语言中有没有类似于此汇编代码的操作?

Is there any operation in C analogous to this assembly code?

今天,我尝试在汇编代码中递增函数指针来创建函数的备用入口点:

.386
.MODEL FLAT, C
.DATA
    INCLUDELIB MSVCRT
    EXTRN puts:PROC
    HLO DB "Hello!", 0
    WLD DB "World!", 0
.CODE
    dentry PROC
        push offset HLO
        call puts           
        add esp, 4
        push offset WLD
        call puts
        add esp, 4
        ret
    dentry ENDP
    main PROC
        lea edx, offset dentry
        call edx
        lea edx, offset dentry
        add edx, 13
        call edx
        ret
    main ENDP
END

(我知道,从技术上讲这段代码是无效的,因为它调用 puts 时没有初始化 CRT,但它在没有任何程序集或运行时错误的情况下工作,至少在 MSVC 2010 SP1 上是这样。)

请注意,在对 dentry 的第二次调用中,我像以前一样在 edx 寄存器中获取了函数的地址,但这次我在调用函数之前将其递增了 13 个字节。

因此这个程序的输出是:

C:\Temp>dblentry
Hello!
World!
World!

C:\Temp>

Hello!\nWorld!”的第一个输出是从函数最开始的调用,而第二个输出是从“push offset WLD”指令开始的调用。

我想知道这种事情是否存在于 C、Pascal 或 FORTRAN 等汇编语言中。我知道 C 不允许你增加函数指针,但有没有其他方法可以实现这种事情?

可以使用longjmp函数:http://www.cplusplus.com/reference/csetjmp/longjmp/

这是一个相当糟糕的函数,但它会满足您的要求。

据我所知,您只能在 asm 中编写具有多个入口点的函数。

您可以在所有入口点上放置标签,这样您就可以使用正常的直接调用,而不是对第一个函数名的偏移量进行硬编码。

这使得从 C 或任何其他语言中正常调用它们变得容易。

如果您担心不允许函数体重叠的工具(或人类)会造成混淆,早期入口点的工作方式类似于落入另一个函数体的函数。


如果早期的入口点做了一些额外的事情,然后进入主函数,您可能会这样做。它主要是一种代码大小节省技术(这可能会提高 I-cache / uop-cache 命中率)。


编译器倾向于在函数之间复制代码,而不是在稍微不同的函数之间共享大块通用实现。

但是,您可能只需要一个额外的 jmp 就可以完成它,例如:

int foo(int a) { return bigfunc(a + 1); }
int bar(int a) { return bigfunc(a + 2); }

int bigfunc(int x) { /* a lot of code */ }

查看 Godbolt compiler explorer

上的真实示例

foobar 尾调用 bigfunc,这比 bar 掉进 bigfunc 稍差。 (让 foo 跳过 bar 进入 bigfunc 仍然很好,特别是如果 bar 不是那么微不足道。)


跳转到一个函数的中间通常是不安全的,因为重要的函数通常需要 save/restore 一些规则。所以序言推动他们,而结语则弹出他们。如果你跳到中间,那么序言中的 pop 就会使堆栈不平衡。 (即,将 return 地址弹出到寄存器中,并将 return 弹出到垃圾地址)。

另见