c-为什么这个代码对单个printf的作用不同?ucontext.h



当我编译下面的代码时,它会打印

我正在运行:(

永远(直到我向程序发送KeyboardInterrupt信号(,
但当我取消注释// printf("done:%dn", done);,重新编译并运行它时,它只打印两次,打印done: 1,然后返回
我是ucontext.h的新手,我对这些代码的工作方式和为什么一个printf会改变代码的整个行为,如果你用done++;替换printf,它也会这样做,但如果用done = 2;替换它,它不会影响任何事情,而且它的工作原理与我们最初对printf的注释一样
有人能解释一下吗:
为什么这个代码会这样,它背后的逻辑是什么
很抱歉我英语不好,
非常感谢。

#include <ucontext.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
register int done = 0;
ucontext_t one;
ucontext_t two;
getcontext(&one);
printf("I am running :)n");
sleep(1);
if (!done)
{
done = 1;  
swapcontext(&two, &one);
}
// printf("done:%dn", done);
return 0;
}

这是一个编译器优化"问题";。当";printf(("则编译器推断出";完成";将不会在";如果(!完成(";,因此它不会将其设置为1,因为它不值得。但是当";printf(("存在;完成";在";如果(!完成(";,所以编译器设置它。

带有";printf((":

$ gcc ctx.c -o ctx -g
$ objdump -S ctx
[...]
int main(void)
{
11e9:   f3 0f 1e fa             endbr64 
11ed:   55                      push   %rbp
11ee:   48 89 e5                mov    %rsp,%rbp
11f1:   48 81 ec b0 07 00 00    sub    $0x7b0,%rsp
11f8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
11ff:   00 00 
1201:   48 89 45 f8             mov    %rax,-0x8(%rbp)
1205:   31 c0                   xor    %eax,%eax
register int done = 0;
1207:   c7 85 5c f8 ff ff 00    movl   $0x0,-0x7a4(%rbp) <------- done set to 0
120e:   00 00 00 
ucontext_t one;
ucontext_t two;
getcontext(&one);
1211:   48 8d 85 60 f8 ff ff    lea    -0x7a0(%rbp),%rax
1218:   48 89 c7                mov    %rax,%rdi
121b:   e8 c0 fe ff ff          callq  10e0 <getcontext@plt>
1220:   f3 0f 1e fa             endbr64 
printf("I am running :)n");
1224:   48 8d 3d d9 0d 00 00    lea    0xdd9(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
122b:   e8 70 fe ff ff          callq  10a0 <puts@plt>
sleep(1);
1230:   bf 01 00 00 00          mov    $0x1,%edi
1235:   e8 b6 fe ff ff          callq  10f0 <sleep@plt>
if (!done)
123a:   83 bd 5c f8 ff ff 00    cmpl   $0x0,-0x7a4(%rbp)
1241:   75 27                   jne    126a <main+0x81>
{
done = 1;  
1243:   c7 85 5c f8 ff ff 01    movl   $0x1,-0x7a4(%rbp) <----- done set to 1
124a:   00 00 00 
swapcontext(&two, &one);
124d:   48 8d 95 60 f8 ff ff    lea    -0x7a0(%rbp),%rdx
1254:   48 8d 85 30 fc ff ff    lea    -0x3d0(%rbp),%rax
125b:   48 89 d6                mov    %rdx,%rsi
125e:   48 89 c7                mov    %rax,%rdi
1261:   e8 6a fe ff ff          callq  10d0 <swapcontext@plt>
1266:   f3 0f 1e fa             endbr64 
}
printf("done:%dn", done);
126a:   8b b5 5c f8 ff ff       mov    -0x7a4(%rbp),%esi
1270:   48 8d 3d 9d 0d 00 00    lea    0xd9d(%rip),%rdi        # 2014 <_IO_stdin_used+0x14>
1277:   b8 00 00 00 00          mov    $0x0,%eax
127c:   e8 3f fe ff ff          callq  10c0 <printf@plt>
return 0;

不带";printf((":

$ gcc ctx.c -o ctx -g
$ objdump -S ctx
[...]
int main(void)
{
11c9:   f3 0f 1e fa             endbr64 
11cd:   55                      push   %rbp
11ce:   48 89 e5                mov    %rsp,%rbp
11d1:   48 81 ec b0 07 00 00    sub    $0x7b0,%rsp
11d8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
11df:   00 00 
11e1:   48 89 45 f8             mov    %rax,-0x8(%rbp)
11e5:   31 c0                   xor    %eax,%eax
register int done = 0;
11e7:   c7 85 5c f8 ff ff 00    movl   $0x0,-0x7a4(%rbp) <------ done set to 0
11ee:   00 00 00 
ucontext_t one;
ucontext_t two;
getcontext(&one);
11f1:   48 8d 85 60 f8 ff ff    lea    -0x7a0(%rbp),%rax
11f8:   48 89 c7                mov    %rax,%rdi
11fb:   e8 c0 fe ff ff          callq  10c0 <getcontext@plt>
1200:   f3 0f 1e fa             endbr64 
printf("I am running :)n");
1204:   48 8d 3d f9 0d 00 00    lea    0xdf9(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
120b:   e8 80 fe ff ff          callq  1090 <puts@plt>
sleep(1);
1210:   bf 01 00 00 00          mov    $0x1,%edi
1215:   e8 b6 fe ff ff          callq  10d0 <sleep@plt>
if (!done)
121a:   83 bd 5c f8 ff ff 00    cmpl   $0x0,-0x7a4(%rbp)
1221:   75 1d                   jne    1240 <main+0x77>
{
done = 1;                             <------------- done is no set here (it is optimized by the compiler)
swapcontext(&two, &one);
1223:   48 8d 95 60 f8 ff ff    lea    -0x7a0(%rbp),%rdx
122a:   48 8d 85 30 fc ff ff    lea    -0x3d0(%rbp),%rax
1231:   48 89 d6                mov    %rdx,%rsi
1234:   48 89 c7                mov    %rax,%rdi
1237:   e8 74 fe ff ff          callq  10b0 <swapcontext@plt>
123c:   f3 0f 1e fa             endbr64 
}
//printf("done:%dn", done);
return 0;
1240:   b8 00 00 00 00          mov    $0x0,%eax
}
1245:   48 8b 4d f8             mov    -0x8(%rbp),%rcx
1249:   64 48 33 0c 25 28 00    xor    %fs:0x28,%rcx
1250:   00 00 
1252:   74 05                   je     1259 <main+0x90>
1254:   e8 47 fe ff ff          callq  10a0 <__stack_chk_fail@plt>
1259:   c9                      leaveq 
125a:   c3                      retq   
125b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

为了禁用"0"上的优化;完成";,添加";挥发性";其定义中的关键字:

volatile register int done = 0;

这使得程序在这两种情况下都能工作。

(与Rachid K的答案有一些重叠,因为它是在我写这篇文章时发布的。(

我猜您是在将done声明为register,希望它能真正放入寄存器中,以便上下文开关保存和恢复它的值。但编译器从来没有义务遵守这一点;大多数现代编译器完全忽略register声明,并自行决定寄存器的使用。特别是,没有优化的gcc几乎总是将局部变量放在堆栈的内存中。

因此,在您的测试用例中,done的值是,而不是由上下文开关恢复的。因此,当getcontext第二次返回时,done的值与调用swapcontext时的值相同。

当存在printf时,正如Rachid也指出的,done = 1实际上存储在swapcontext之前,因此在第二次返回getcontext时,done的值为1,跳过if块,程序打印done:1并退出。

然而,当printf不存在时,编译器会注意到done的值在赋值后从未使用过(因为它假设swapcontext是一个正常函数,并且不知道它实际上会返回到其他地方(,所以它会优化死存储(是的,即使优化已关闭(。因此,当getcontext第二次返回时,我们得到了done == 0,并且得到了一个无限循环。如果你认为done会被放在寄存器中,这可能是你所期望的,但如果是这样,你会得到";右";出于错误原因的行为。

如果启用优化,您将再次看到其他内容:编译器注意到done不会受到对getcontext的调用的影响(再次假设它是一个正常的函数调用(,因此它在if处保证为0。所以这个测试根本不需要做,因为它永远都是真的。然后无条件地执行swapcontext,而对于done,它被优化得完全不存在,因为它不再对代码有任何影响。你将再次看到一个无限循环。

由于这个问题,您真的无法对在getcontextswapcontext之间修改的局部变量做出任何安全的假设。当getcontext第二次返回时,您可能看到也可能看不到更改。如果编译器选择围绕函数调用对某些代码进行重新排序,就会出现进一步的问题(它不知道为什么不这样做,因为它再次认为这些是普通的函数调用,看不到局部变量(。

获得任何确定性的唯一方法是声明一个变量volatile。然后,您可以确保看到的中间更改,并且编译器不会认为getcontext无法更改它。在getcontext的第二次返回中看到的值将与调用swapcontext时看到的值相同。如果你写CCD_ 40,你应该只看到两个";我在跑步;消息,而不管其他代码或优化设置如何。

相关内容

  • 没有找到相关文章

最新更新