代码之家  ›  专栏  ›  技术社区  ›  Max

在C#中设置jmp/longjmp。有可能吗?

c#
  •  3
  • Max  · 技术社区  · 14 年前

    当需要转到本地范围时,我遇到了一个问题:

    if(...)      
    {
       DoSomethingHere();
       if (...) goto Label;
    }
    else if(...)
    {
    Label:
      DoSomethingHereToo();
    }
    

    ,这在C#中显然是不可能的。

    是的,我知道使用goto被认为是一种不好的做法,但是在这种情况下,使用goto更容易。所以我不想进入整个“goto是万恶之源”的讨论。对我来说,一个更有趣、更普遍的问题是在C#中使用setjmp/longjmp的可能性。那有可能吗?

    9 回复  |  直到 14 年前
        1
  •  12
  •   Eric Lippert    14 年前

    首先,我认为你把跳远和跳远混为一谈,把跳远和跳远混为一谈。一个典型的C型跳远可以从两个方面来考虑:一,它就像抛出一个不清理堆栈帧的异常。第二,这就像从函数返回到“错误”地址。

    在C#中,以上任何一项都不可能。C不支持跳远;我们已经尝试接球最后投掷,以一种干净、结构化和安全的方式进行非本地goto。

    C也不支持从局部变量声明空间外部到空间内部的短跳转。原因是,从外面跳到一个街区的中间是令人困惑、危险、难以理解和难以维持的。这个设计目标的实现方式是 标签 也一样 范围 作为局部变量。“goto”甚至都看不到标签,比该位置的代码在不同的局部变量声明空间中声明的局部变量还要多。

    有很多方法可以解决你的问题而不使用任何goto语句。例如,一想到

    bool doFirstThing = false;
    bool doSecondThing = false;
    if (firstCondition) 
    {
        doFirstThing = true;
        doSecondThing = true;
    }
    else if (secondCondition)
    {
        doSecondThing = true;
    }
    if (doFirstThing) 
    {
        DoFirstThing();
    }
    if (doSecondThing)
    {
        DoSecondThing();
    }
    

    这非常简单,易于阅读,易于调试,等等。

    或者:如果“doSecondThing”结果中的共享代码实际上很难重构成它自己的方法,那么考虑后退一步,并决定您的控制流是否太复杂而无法开始。例如,如果你在一个循环中对很多变量进行变异,那么也许有一些技术可以用来简化这种情况,减少变异。你能提供更多关于这段代码正在做什么以及为什么很难重构它的信息吗?

        2
  •  3
  •   Brian    14 年前

    不要做下面描述的任何事情。这是一个坏主意,只是为了提供信息或作为智力练习。


    C语言不支持跳出范围。所以,不可能按照你在C#里的要求去做。然而, 伊利诺伊州 将允许您这样做,因为IL低于C#,并且没有这种形式的范围。

    所以,如果你真的想在忽略作用域的同时支持goto,你可以使用后期编译器工具来修改IL。类似于 Mike Stall's tool . 注: 这是一个可怕的,可怕的主意。 我甚至认为这是一种智力活动,我应该感到羞愧。

    如果实际尝试为生产代码执行此操作,则此操作将中断的原因:

    • 编译器会忽略您的特殊代码,因此它可能会以“安全”的方式重新排列您的代码,而不考虑您的非本地goto。
    • 编写一个C编译器是很困难的。创建一个后处理器来将随机IL嵌入到代码中并不是很困难(特别是以Mike Stall的工具为起点),但是以一种可靠的方式来实现它是非常困难的。
    • C不支持非本地goto是有原因的;当您使用非本地goto时,很容易编写损坏的代码。更糟糕的是,如果你在C编译器周围做了一个结束运行,并试图强迫它自己工作。
        3
  •  3
  •   IS4    8 年前

    这里有龙。

    试图回答标题中的问题,我的第一次尝试自然是通过互操作和导入 设置JMP 龙吉普 从msvcrt.dll。

    [DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint="_setjmp")]
    static extern int setjmp(out Jmp_buf env);
    
    [DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
    static extern void longjmp(ref Jmp_buf env, int val);
    
    [StructLayout(LayoutKind.Sequential,Size=16*4)]
    struct Jmp_buf{}
    

    看来我已经使导入签名正确了,但最终,它不能这样工作。P/Invoke在对本机setjmp的调用周围创建一个包装器,因此 setjmp 在P/Invoke方法返回时已释放。这并不奇怪 longjmp 投掷 访问异常 那么。

    就这样。有 不可能 在山上。。。我的意思是,用纯C来调用这两个函数。我能想到的唯一方法是通过破解某些方法的JITted本机代码来“内联”调用,并手动包含对 设置JMP . 很抱歉,即使是我也没有理由在没有任何正当理由的情况下这样做。

    但是如果我不能从C语言调用函数,我当然可以从C++ +CLI!

    #include <csetjmp>
    #include <iostream>
    using namespace System;
    using namespace System::Runtime::InteropServices;
    using namespace std;
    
    typedef void (*UnmanagedHandler)(int code);
    
    void mysetjmp(jmp_buf env, UnmanagedHandler handler)
    {
        handler(setjmp(env));
        throw 0;
    }
    
    void mylongjmp(jmp_buf env, int val)
    {
        longjmp(env, val);
    }
    
    namespace jmptestdll
    {
        public delegate void JumpHandler(int code);
    
        public ref class JumpBuffer
        {
        private:
            jmp_buf *env;
    
        public:
            JumpBuffer()
            {
                env = new jmp_buf[1];
            }
    
            ~JumpBuffer()
            {
                this->!JumpBuffer();
            }
    
            void Set(JumpHandler^ handler)
            {
                if(env)
                {
                    IntPtr ptr = Marshal::GetFunctionPointerForDelegate(handler);
                    UnmanagedHandler act = static_cast<UnmanagedHandler>(ptr.ToPointer());
                    try{
                        mysetjmp(*env, act);
                    }catch(int code)
                    {
    
                    }
                }
            }
    
            void Jump(int value)
            {
                if(env)
                {
                    mylongjmp(*env, value);
                }
            }
    
        protected:
            !JumpBuffer()
            {
                if(env)
                {
                    delete[] env;
                }
            }
        };
    }
    

    我可能在那个代码中犯了一些可怕的错误,但是C++不是我的母语,对不起。不过,它确实起到了作用。不知为什么,从 mysetjmp 投掷 访问异常 但我还没有找到原因。”返回“via throw 作品。

    var env = new JumpBuffer();
    env.Set(
        delegate(int code)
        {
            Console.WriteLine(code);
            env.Jump(code+1);
            Console.WriteLine("Goodbye world!");
        }
    );
    

    “再见世界!”从不显示,而是显示从0开始递增的数字。移植 Wikipedia example 同样有效:

    static JumpBuffer buf = new JumpBuffer();
    
    static void second()
    {
        Console.WriteLine("second");
        try{
            buf.Jump(1);
        }finally{
            Console.WriteLine("finally");
        }
    }
    
    static void first()
    {
        second();
        Console.WriteLine("first");
    }
    
    public static void Main(string[] args)
    {
        buf.Set(
            val => {
                Console.WriteLine(val);
                if(val == 0) first();
                else Console.WriteLine("main");
            }
        );
    
        Console.ReadKey(true);
    }
    

    输出:

    0个
    第二
    最后
    1个
    主要的

    一开始我还以为它会跳过 finally 但我想没那么邪恶。唯一的缺点是我们不能在 Set 而必须传递一个处理程序。

    如果要跳转到任意方法到任意标签,请创建状态机。

    嗯,C中有一些内部状态机支持迭代器和 async . 迭代器对于我们来说太有限了,但是 await ,这可能正是我们所需要的。

    public class LongJump
    {
        Continuation continuation;
    
        public SetAwaiter Set()
        {
            return new SetAwaiter(this);
        }
    
        public JumpAwaiter Jump()
        {
            return new JumpAwaiter(this);
        }
    
        public struct JumpAwaiter : INotifyCompletion
        {
            readonly LongJump jump;
    
            public JumpAwaiter(LongJump jump)
            {
                this.jump = jump;
            }
    
            public JumpAwaiter GetAwaiter()
            {
                return this;
            }
    
            public bool IsCompleted{
                get{
                    return false;
                }
            }
    
            public void OnCompleted(Action callerContinuation)
            {
                jump.continuation.Continue();
            }
    
            public void GetResult()
            {
    
            }
        }
    
        public struct SetAwaiter : INotifyCompletion
        {
            readonly LongJump jump;
    
            public SetAwaiter(LongJump jump)
            {
                this.jump = jump;
            }
    
            public SetAwaiter GetAwaiter()
            {
                return this;
            }
    
            public bool IsCompleted{
                get{
                    return false;
                }
            }
    
            public void OnCompleted(Action callerContinuation)
            {
                jump.continuation = new Continuation(callerContinuation);
                callerContinuation();
            }
    
            public void GetResult()
            {
    
            }
        }
    
        private class Continuation
        {
            private readonly int savedState;
            private readonly object stateMachine;
            private readonly FieldInfo field;
            private readonly Action action;
    
            internal Continuation(Action action)
            {
                stateMachine = action.Target.GetType().InvokeMember("m_stateMachine", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField, null, action.Target, null);
                field = stateMachine.GetType().GetField("<>1__state", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                savedState = (int)field.GetValue(stateMachine);
                this.action = action;
            }
    
            internal void Continue()
            {
                field.SetValue(stateMachine, savedState);
                action();
            }
        }
    }
    

    Â

    public static void Main(string[] args)
    {
        MainAsync().Wait();
    
        Console.ReadKey(true);
    }
    
    public static async Task MainAsync()
    {
        var jump = new LongJump();
        Console.WriteLine("Begin");
        int code = 0;
        await jump.Set();
        Console.WriteLine(code);
        code += 1;
        await InnerMethod(code, jump);
        Console.WriteLine("End");
    }
    
    public static async Task InnerMethod(int code, LongJump jump)
    {
        if(code < 5)
        {
            await jump.Jump();
        }
    }
    

    我从乔恩·斯凯特的作品中得到了一些灵感 article 关于在C#中实现COMEFROM。

    总结这段代码,调用 await jump.Set(); 实际上记住状态机在那一刻的状态,然后像往常一样继续执行。 await jump.Jump(); 放弃该行之后的任何延续,并恢复旧的延续。你甚至可以跳转到一个已经结束的方法中,但不要从中返回,因为它会试图再次标记它的任务已完成,从而导致异常。

    我想也有可能将异步方法与C++ /CLI代码结合起来,以去除 等待 jump.Jump() ,但这并不太有用。

    请记住,在C#-异常处理中已经有了一个有用的“跳远”机制。

        4
  •  1
  •   Sam Skuce    14 年前

    为什么不:

    condition1Cache = condition1;
    condition2Cache = false;
    if ( condition1Cache )
    {    
       yadda yadda
       condition2Cache = condition2;
    }
    /* short-circuit evaluation will prevent condition3 from being evaluated (and possibly having side-effects) in a manner compatible with the original code. */
    if ( ( condition1Cache && condition2Cache ) || (!condition1Cache && condition3) ) 
    {
       bada bing
    }
    

    工作?

    编辑:已更新为使用缓存,以避免在您不希望的情况下出现导致副作用的情况。

        5
  •  1
  •   supercat    14 年前

    如果条件和生成的代码可以用rvalues表示,则可以使用短路操作来执行在不使用goto或标志的情况下不可能执行的操作。就你而言:

    if (condition1() ? (DoSomethingHere(),condition2()) : condition3())
      DoSomethingHere2();
    

    可能不是我通常如何编写它,除非DoSomethingHere显然与条件2的求值相关联,但它应该产生所需的语义。我不确定我是否希望编译器能够识别?:根据条件(与将其作为零/非零结果进行求值和基于此进行条件跳转不同)。

    顺便说一句,比起goto,我更讨厌标志的某些用法,因为每个标志都会给程序流添加另一个“维度”——如果一个正在绘制程序流图,那么程序中任何给定位置上可能相关的标志的每个不同组合都代表一个不同的节点。如果必要的执行模式可以通过“goto”和“no”标志来实现,那么这可能比使用标志和“goto”更好。

        6
  •  0
  •   Mark Avenius    14 年前

    根据您的代码,以下内容是等效的:

    if(condition1)      
    {
       DoSomethingHere();
    }
    
    if(condition2)
    {
       DoSomethingHereToo();
    }
    

    你能提供更多的上下文来说明为什么这不起作用吗?

        7
  •  0
  •   Scott Chamberlain    14 年前

    这里有一种方法,我不确定它是否是最好的,不用去做你想做的事

    bool doSomthingElseFlag = false
    if(...)      
    {
       DoSomethingHere();
       if (...)
          doSomthingElseFlag = true;
    }
    else if(...)
    {
      DoSomethingHere2();
      if (...)
         doSomthingElseFlag = true;
    }
    else if(...)
    {
      //Not This function does not need the other function to run
      //so we do not set the flag
      DoSomethingHere3();
    }
    if (doSomthingElseFlag)
    {
      DoSomethingElse();
    }
    
        8
  •  0
  •   Glorfindel Doug L.    5 年前

    关于setjmp/longjmp的主题,您可以使用它的bigger brother: continuations . C语言不支持它们,但您可以使用 continuation passing style ,结合 lazy evaluation trampolines 所以你得不到 stack overflow .

        9
  •  -1
  •   Krishna S Santosh    8 年前

    是的,这是可能的。考虑一下:

       void method1()
        {
           for (var i = 0; i < 100; i++)
           {
               method2(i);
               Console.WriteLine(i);
    
                EndOfLoop: //This is something like a setjmp marker
                        ;
             }
        }
    
        void method2(int i)
        {
           if (i%10 == 0)
               Console.WriteLine("Next Number can be divided by 10");
    
       // Now Long jmp to EndOfLoop
        #if IL
            br EndOfLoop 
        #endif
        }
    

    但是,它很快就会使您的代码不可读:)

    代码的输出为:

    1个 2个 三 4个 5个 6个 7个 八 9个 下一个数字可以除以10 10 11。。。。。各就各位。

    方法1的ILDASM输出:

    .method private hidebysig static void  method1() cil managed
    {
      // Code size       35 (0x23)
      .maxstack  2
      .locals init ([0] int32 i,
               [1] bool CS$4$0000)
      IL_0000:  nop
      IL_0001:  ldc.i4.0
      IL_0002:  stloc.0
      IL_0003:  br.s       IL_0019
      IL_0005:  nop
      IL_0006:  ldloc.0
      IL_0007:  call       void Test::method2(int32)
      IL_000c:  nop
      IL_000d:  ldloc.0
      IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
      IL_0013:  nop
      IL_0014:  nop
      IL_0015:  ldloc.0
      IL_0016:  ldc.i4.1
      IL_0017:  add
      IL_0018:  stloc.0
      IL_0019:  ldloc.0
      IL_001a:  ldc.i4.s   100
      IL_001c:  clt
      IL_001e:  stloc.1
      IL_001f:  ldloc.1
      IL_0020:  brtrue.s   IL_0005
      IL_0022:  ret
    } // end of method Test::method1
    

    方法2的ILDASM输出:

    .method private hidebysig static void  method2(int32 i) cil managed
    {
      // Code size       27 (0x1b)
      .maxstack  2
      .locals init ([0] bool CS$4$0000)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  ldc.i4.s   10
      IL_0004:  rem
      IL_0005:  ldc.i4.0
      IL_0006:  ceq
      IL_0008:  ldc.i4.0
      IL_0009:  ceq
      IL_000b:  stloc.0
      IL_000c:  ldloc.0
      IL_000d:  brtrue.s   IL_001a
      IL_000f:  ldstr      "Next Number can be divided by 10"
      IL_0014:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0019:  nop
      IL_001a:  ret
    } // end of method Test::method2
    

    执行的示例输出:

    enter image description here