如何在C语言中跳转到不同的函数?

27

我基本上正在尝试用C语言模拟汇编代码。

这是C代码:

int main()
{
   test();
main_next:
   printf("Hello, World!");
}

void test()
{
     goto main_next;
}

在尝试编译此代码(Linux 32位,gcc 4.6.3)时,我遇到了以下错误:

 error: label ‘main_randomtag_next’ used but not defined

有人知道如何在C语言中实现这种跨过程的goto吗?

谢谢!


15
即使这是可能的,这也不是你应该想要能够做到的事情。 - Oliver Charlesworth
6
可以使用setjmp/longjmp实现。具体请参考http://en.wikipedia.org/wiki/Setjmp.h - Kyle Lutz
10
注意:给那个点了“-1”的人,我点了“+1”,因为提问者做了一些研究,问题很清楚合法,只是他想做的事情不好,我们有评论和答案给他更好的选择! - zmo
5
如果你在纯C中模拟汇编,为什么会有函数?汇编没有函数,而是使用堆栈。相反,你可以模拟堆栈来实现。 - Sedat Kapanoglu
3
这是一个非常有趣的问题。我编辑了您的帖子,以使其更易读,除此之外,我认为这是一个完全合理的问题。 - michaelsnowden
显示剩余7条评论
4个回答

35

那么子程序 呢? 栈怎么办?

如果你考虑栈的话,goto 在函数之间是没有意义的。当你跳转时,栈上会有什么内容?源函数和目标函数可能具有不同的参数和返回值。新函数将返回给谁?它的返回值是否对调用者有意义?调用者调用的是源函数,而不是目标函数。

返回给调用者?

仔细考虑一下你的例子:

int main()
{
   test();
main_next:
   printf("hello, world");
}

void test()
{
     goto main_next;
}
goto被执行时会发生什么?我认为你希望它跳回到调用main()函数的位置,也就是向上跳出堆栈。这样,goto实际上就相当于一个return,可以改变调用堆栈的状态,变为:

What happens when the goto executes? I presume you'd want this to jump up the stack back to the calling main() function. The goto would effectively be the same as a return, changing the call stack from:

main()                            main()
|                   to            
+--> test()                       

但是如果您想要跳转到不在调用栈中的函数呢?那么怎么办呢?

或者替换当前函数?

另一种解释是,goto将用main()代替现有的test()调用。调用栈将从以下内容更改:

main()                            main()
|                   to            |
+--> test()                       +--> main()

现在,main() 在递归调用自身,而更低级别的 main() 将返回到更高级别的 main()——顺便说一下,更高级别的函数期望得到一个 void 返回值,但实际上会收到一个 int 返回值。

setjmp 和 longjmp

你可以通过使用setjmp / longjmp 来实现类似效果。这些函数可以保存和恢复非本地跳转的堆栈上下文,允许你在函数调用之间进行跳转。

setjmplongjmp 通过 (a) 在跳转时保存和恢复完整的堆栈上下文,以及 (b) 如果堆栈上下文无效,则不允许跳转,解决了我所描述的问题。 我从手册页中引用(重点是我的):

setjmp() 和 longjmp(3) 可以用于处理程序的低级子例程中遇到的错误和中断。setjmp() 会将堆栈上下文/环境保存在 env 中,以供 longjmp(3) 稍后使用。 如果调用 setjmp() 的函数返回,则堆栈上下文将无效。

换句话说,longjmp 基本上是 C 中抛出异常的等价物。低级别的函数可以解开调用堆栈并在更高级别函数中继续执行。

它也很棘手,很少是个好主意。再次引用手册页:

setjmp() 和 sigsetjmp() 使程序难以理解和维护。如果可能,应该使用其他方法。


9
GCC先生成汇编文件,然后再将其汇编,那么如何使用内联汇编创建标签呢?
void test()
{
    __asm__ volatile ( 
         "jmp main_next"
    );
}


int main()
{
    test();
    __asm__ volatile ( 
        "main_next:"
    );
    printf("hello, world");
}

然而,显然这种方法不应该在实际情况中使用,因为它根本没有考虑堆栈的问题。


4
使用内联汇编来执行编译器不希望出现的操作是创建无法工作的程序的许多方法之一。您可以使用“asm goto”告诉编译器您的汇编代码可以跳转到哪里,但出于与John所解释的相同的原因,只能在当前函数内进行跳转。 - Peter Cordes
@PeterCordes 实际上我查看这个问题的原因是因为我不想在执行调用时将当前指令指针推入堆栈。我与原帖作者有相同的目标,即解释字节码。但我打算使用线程化代码来实现它,其中每个指令都跳到下一个指令,没有涉及任何堆栈操作。 - Raslanove
1
@Raslanove:如果你想让它工作,所有的代码都必须在同一个函数中,这样才有可能是安全的,然后你可以使用普通的gotoswitch。当执行没有从asm语句的开头进入时,你永远不能安全地让执行掉出asm语句的末尾。(除了在调试构建中,每个C语句都编译为单独的块,这样你就可以用调试器在行之间跳转,但调试构建与性能不兼容。) - Peter Cordes
1
@Raslanove:另请参见X86预取优化:“计算跳转”线程代码,关于跳转线程与单个间接分支调度所有内容的比较。后者通常在Haswell及更高版本的现代分支预测策略(IT-TAGE)中表现良好,但也请参见http://www.emulators.com/docs/nx25_nostradamus.htm,了解有关跳转线程的一篇好文章。 - Peter Cordes
1
@PeterCordes 我得出了相同的结论,将所有内容放在同一个函数中对编译器友好。我甚至可以避免使用汇编语言,并获得跨平台的好处。相反,我最终使用了“void *ptr = &&labell;”和“goto *ptr;”。我们可以建立查找表,并且甚至可以使用指针算术运算。 顺便说一句,非常感谢您提供的参考资料 :) - Raslanove

2

好的,最好能够参考一下http://c-faq.com/style/stylewars.html网站上的智慧!

基本上,如果你想要使用C语言模拟汇编语言的行为,那么你应该充分利用C / C ++的所有分支能力。而使用函数和函数堆栈实际上比使用goto和标签更有效。这就是所谓的结构化编程,正如@ssg明智地指出的那样!


并非在所有情况下都是一种改进,因此说它是一种改进取决于具体情况。Goto和标签更加灵活,并且保持相同的堆栈上下文。 - Andrew

1
不允许从一个函数内部跳转到另一个函数。问题在于函数“test”在堆栈上具有返回地址和可能的变量框架。因此,为了实现这一点,您应该清除可选框架,并将堆栈上的地址更改为main_next的地址。
因此,在这个基本示例中,你只需要写return而不是goto main_next。
但在其他情况下,情况会稍微复杂一些,因为您必须理解您想要什么。
您需要将main_next之后的代码视为在test()中编写吗?您应该记住这两个函数的局部变量框架是不同的。这意味着如果您只是进行跳转,那么您将使用main中使用的变量名称,但是您将引用由test()创建的堆栈帧。这意味着如果这两个框架不兼容,则可能会发生非常奇怪的事情。
问题是您确切地想要什么以及为什么?
如果您只考虑汇编语言,并且不使用堆栈帧中的变量,则可以。但是,如果没有变量,您将要做什么?
有方法可以做到您想要的,但是您应该决定您需要什么,我可以告诉您如何实现!

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