对使用 longjmp 恢复的事情感到困惑吗?

huangapple go评论54阅读模式
英文:

Confusing about things restored with longjmp?

问题

在这里提到:https://stackoverflow.com/a/58498330 当使用 setjmplongjmp,并返回到 setjmp 的作用域时,所有可访问的对象的值都与调用函数 longjmp 时相同。

我对此感到困惑,因为 longjmp 把我们带到程序中完全不同的地方,在我们调用 longjmp 时的局部变量毫无意义。所以当变量都不同的时候,它们怎么可能是“相同”的呢?

英文:

As mentioned here: https://stackoverflow.com/a/58498330 when using setjmp and longjmp, and returning to the scope of setjmp, all accessible object have the same value as when the function longjmp is called.

I am confused about this, because longjmp takes us to a completely different place in the program, where the local variables when we call longjmp make zero sense. So how can the value be "the same" when the variables are all different?

答案1

得分: 2

关键词在于 accessible。通过调用 longjmp 恢复的栈部分上的局部变量将不再存在,因此不再可访问。就此而言,故事结束。

值得指出的是,这些变量的析构函数(如果有的话)将不会被调用1。因此,不要在 C++ 代码中使用 longjmp。相反,应使用 C++ 异常处理,以受控方式恢复栈。

1我认为 MSVC 实际上会调用它们,但如果您希望代码具有可移植性,不应依赖于此。

英文:

The key word here is accessible. Local variables on the part of the stack that is unwound by calling longjmp cease to exist and are therefore no longer accessible. End of story.

It's worth pointing out that the destructors of those variables (if any) won't be called<sup>1</sup>. For this reason, don't use longjmp in C++ code. Use C++ exception handling instead, which unwinds the stack in a controlled way.

<sup>1</sup>I think MSVC does actually call them, but this is not something you should rely on if you want your code to be portable.

答案2

得分: 2

以下是您要翻译的内容:

有关“可访问”变量被还原和未更改的误解。

引用中的短语是:

在返回到setjmp范围时,所有可访问的对象、浮点状态标志和抽象机器的其他组件都具有与执行std::longjmp时相同的值,但setjmp范围中的非易失性本地变量的值是不确定的,如果它们自setjmp调用以来已更改,则为不确定。

当使用先前由setjmp调用设置的jmp_buf并且其调用帧仍然在调用堆栈中处于活动状态时,调用longjmp时,会从jmp_buf中保存的数据中恢复一部分处理器状态,如一些寄存器和标志,并将控制转移到setjmp调用的返回地址,丢弃所有中间帧,而不调用任何析构函数或最终代码。唯一受影响的对象是setjmp范围内的非易失性本地变量,它们可能已从jmp_buf数据中还原,也可能没有,因此具有不确定的值。

这是一个示例:

#include <stdio.h>
#include <setjmp.h>

jmp_buf buf;
int v1;
volatile int v123 = 123, v456 = 456;

void willjmp(void) {
    longjmp(buf, 42);
}

int main(void) {
    volatile int v2;
    int val, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12;

    // 初始值
    v1 = v123; v2 = v123; v3 = v123; v4 = v123;
    v5 = v123; v6 = v123; v7 = v123; v8 = v123;
    v9 = v123; v10 = v123; v11 = v123; v12 = v123;

    if ((val = setjmp(buf)) != 0) {
        printf("\nafter longjmp:\n");
        printf("val=%d\n", val);   // 打印 val=42
        printf("v1=%d\n", v1);     // 打印 v1=456
        printf("v2=%d\n", v2);     // 打印 v2=456
        // 非易失性本地变量具有不确定的值
        printf("v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
    } else {
        printf("初始值:\n");
        printf("val=%d\n", val);   // 打印 val=42
        printf("v1=%d\n", v1);     // 打印 v1=123
        printf("v2=%d\n", v2);     // 打印 v2=123
        // 打印 v3..v12=123,123,123,123,123,123,123,123,123,123
        printf("v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
        v1 = v456; v2 = v456; v3 = v456; v4 = v456;
        v5 = v456; v6 = v456; v7 = v456; v8 = v456;
        v9 = v456; v10 = v456; v11 = v456; v12 = v456;
        printf("\nbefore longjmp:\n");
        printf("v1=%d\n", v1);     // 打印 v1=456
        printf("v2=%d\n", v2);     // 打印 v2=456
        // 打印 v3..v12=456,456,456,456,456,456,456,456,456,456
        printf("v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
        willjmp();
    }
    return 0;
}

输出:

初始值:
val=0
v1=123
v2=123
v3..v12=123,123,123,123,123,123,123,123,123,123

before longjmp:
v1=456
v2=456
v3..v12=456,456,456,456,456,456,456,456,456,456

after longjmp:
val=42
v1=456
v2=456
v3..v12=123,123,123,123,123,123,123,123,123,123

根据处理器架构、编译器实现和配置,特别是优化标志,v3v16的输出可能是456123,或者甚至可能是其他内容。上面的输出是在我的M2笔记本上启用了优化的情况下生成的。如果禁用优化,最后一行将变为v3..v12=456,456,456,456,456,456,456,456,456,456

正如可以在Goldbolt的Compiler Explorer上看到的,不同的编译器产生不同的输出,例如gcc -O输出v3..v12=123,123,123,123,123,123,456,456,456,456

英文:

There is a misunderstanding about what accessible variables are restored and unchanged.

The phrase in reference is:

> Upon return to the scope of setjmp, all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when std::longjmp was executed, except for the non-volatile local variables in setjmp's scope, whose values are indeterminate if they have been changed since the setjmp invocation.

When longjmp is called with a jmp_buf previously set by a call to setjmp whose calling frame is still active in the call stack, part of processor state such as some of the registers and flags are restored from data saved in the jmp_buf and control is transferred back to the return address of the setjmp call, discarding all intermediary frames without calling any destructors or finalisation code. The only objects that are affected are non-volatile local variables in the setjmp scope, which may have been restored from the jmp_buf data or not, and thus have indeterminate values.

Here is an example:

#include &lt;stdio.h&gt;
#include &lt;setjmp.h&gt;

jmp_buf buf;
int v1;
volatile int v123 = 123, v456 = 456;

void willjmp(void) {
    longjmp(buf, 42);
}

int main(void) {
    volatile int v2;
    int val, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12;

    // initial values
    v1 = v123; v2 = v123; v3 = v123; v4 = v123;
    v5 = v123; v6 = v123; v7 = v123; v8 = v123;
    v9 = v123; v10 = v123; v11 = v123; v12 = v123;

    if ((val = setjmp(buf)) != 0) {
        printf(&quot;\nafter longjmp:\n&quot;);
        printf(&quot;val=%d\n&quot;, val);   // prints val=42
        printf(&quot;v1=%d\n&quot;, v1);     // prints v1=456
        printf(&quot;v2=%d\n&quot;, v2);     // prints v2=456
        // non volatile local variables have indeterminate values
        printf(&quot;v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n&quot;,
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
    } else {
        printf(&quot;initial values:\n&quot;);
        printf(&quot;val=%d\n&quot;, val);   // prints val=42
        printf(&quot;v1=%d\n&quot;, v1);     // prints v1=123
        printf(&quot;v2=%d\n&quot;, v2);     // prints v2=123
        // prints v3..v12=123,123,123,123,123,123,123,123,123,123
        printf(&quot;v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n&quot;,
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
        v1 = v456; v2 = v456; v3 = v456; v4 = v456;
        v5 = v456; v6 = v456; v7 = v456; v8 = v456;
        v9 = v456; v10 = v456; v11 = v456; v12 = v456;
        printf(&quot;\nbefore longjmp:\n&quot;);
        printf(&quot;v1=%d\n&quot;, v1);     // prints v1=456
        printf(&quot;v2=%d\n&quot;, v2);     // prints v2=456
        // prints v3..v12=456,456,456,456,456,456,456,456,456,456
        printf(&quot;v3..v12=%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n&quot;,
               v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
        willjmp();
    }
    return 0;
}

Output:

initial values:
val=0
v1=123
v2=123
v3..v12=123,123,123,123,123,123,123,123,123,123

before longjmp:
v1=456
v2=456
v3..v12=456,456,456,456,456,456,456,456,456,456

after longjmp:
val=42
v1=456
v2=456
v3..v12=123,123,123,123,123,123,123,123,123,123

Depending on the processor architecture, the compiler implementation and configuration, especially optimisation flags, the output for v3 through v16 could be 456 or 123 or possibly even something else. The output above was produced on my M2 laptop with optimisations enabled. With optimisations disabled, the last line becomes v3..v12=456,456,456,456,456,456,456,456,456,456.

As can be seen on Goldbolt's Compiler Explorer, different compilers produce different output, for example gcc -O outputs v3..v12=123,123,123,123,123,123,456,456,456,456

huangapple
  • 本文由 发表于 2023年5月6日 16:33:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76187921.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定