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

在C#中设置jmp/longjmp。可能吗?,c#,C#,当我需要转到本地范围时,我遇到了一个问题: if(...) { DoSomethingHere(); if (...) goto Label; } else if(...) { Label: DoSomethingHereToo(); } ,这显然在C#中是不可能的 是的,我知道使用goto被认为是一种不好的做法,但在这种情况下,使用goto要容易得多。所以我不想进入整个“goto’s是万恶之源”的讨论。对我来说,一个更有趣、更普遍的问题是C#中setjmp/lon

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

if(...)      
{
   DoSomethingHere();
   if (...) goto Label;
}
else if(...)
{
Label:
  DoSomethingHereToo();
}
,这显然在C#中是不可能的


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

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

if(condition1)      
{
   DoSomethingHere();
}

if(condition2)
{
   DoSomethingHereToo();
}

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

这里有一种方法,我不确定它是否是最好的,可以做你想做的事情,而不需要goto's

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();
}
为什么不:

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
}
工作


编辑:更新为使用缓存,以避免在您不希望出现副作用的情况下产生副作用的可能性。

首先,我认为您混淆了在本地范围内执行“转到”操作(短跳转)和跳远操作(完全在当前方法之外执行转到某个位置)。一个经典的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();
}
这非常简单,易于阅读,易于调试,等等


备选地:如果“DuffEndodTy”结果中的共享代码实际上难以重构成自己的方法,那么请考虑退一步,并决定您的控制流是否过于复杂而无法开始。例如,如果你在一个循环中变异很多变量,那么也许有一些技术可以用来简化这种情况,减少变异。您能否提供有关此代码正在执行的操作以及重构它的困难原因的更多信息?

如果条件和生成的代码可以表示为右值,则您可以使用短路操作来执行在不使用gotos或标志的情况下无法执行的操作。就你而言:

if (condition1() ? (DoSomethingHere(),condition2()) : condition3()) DoSomethingHere2(); 如果(条件1()?(DoSomethingHere(),条件2()):条件3()) DoSomethingHere2(); 可能不是我通常的编码方式,除非DoSomethingHere与condition2的计算明确关联,但它应该产生所需的语义。我不确定我是否希望编译器识别?:对条件的影响(不同于将其作为零/非零结果进行计算并在此基础上进行条件跳转)


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

关于setjmp/longjmp主题,您可以使用它的bigger brother:。C#在语言中不支持它们,但您可以使用,结合使用,或者您不会得到a。

不要执行下面描述的任何操作。这是一个坏主意,只用于提供信息或作为智力练习。
C语言不支持跳出范围。所以,在C#中,你不可能做到你所要求的。然而,IL将允许您这样做,因为IL的级别低于C#,并且实际上没有这种形式的作用域

因此,如果您真的想在忽略作用域的同时支持goto,您可以使用后期编译器工具来调整IL。类似于。注意:这是一个可怕的想法。我甚至认为这是一个智力练习,我应该感到羞愧

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

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

  • 是的,这是可能的。考虑这一点:

       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
        }
    
    方法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
    
    执行的示例输出:


    这里有龙。

    为了回答标题中的问题,我的第一次尝试自然是通过互操作并从msvcrt.dll导入setjmp和longjmp

    [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的调用创建一个包装器,因此当P/Invoke方法返回时,
    setjmp
    的堆栈框架已经被释放。这并不奇怪
    longjmp
    抛出AccessViolationE
    [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{}
    
    #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;
                }
            }
        };
    }
    
    var env = new JumpBuffer();
    env.Set(
        delegate(int code)
        {
            Console.WriteLine(code);
            env.Jump(code+1);
            Console.WriteLine("Goodbye world!");
        }
    );
    
    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);
    }
    
    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();
        }
    }