在stackoverflow上看到这个问题
原答主已经回答的很仔细了,不过还不够直观,这里做个试验
1 #include2 #include 3 4 //加子函数防止变量优化没了 5 void pfunc(int a, int b) 6 { 7 int i = 0; 8 int sum = 0; 9 for (i = 0; i < b; i++) 10 { 11 sum += a * i; 12 } 13 14 printf("sum %x\n", sum); 15 } 16 17 int main() 18 { 19 int i = 0x102; 20 int a = (volatile int)i; 21 int b = *(volatile int *)&i; 22 23 pfunc(a, b); 24 }
在arm平台上编译并截取main()的汇编 R0与R1为函数入参,从ldr指令看两者获取i的方式相同
1 000083ec: 2 83ec: e92d4800 push {fp, lr} 3 83f0: e28db004 add fp, sp, #4 4 83f4: e24dd010 sub sp, sp, #16 5 83f8: e59f3028 ldr r3, [pc, #40] ; 8428 6 83fc: e50b3010 str r3, [fp, #-16] 7 8400: e51b3010 ldr r3, [fp, #-16] 8 8404: e50b3008 str r3, [fp, #-8] 9 8408: e51b3010 ldr r3, [fp, #-16] 10 840c: e50b300c str r3, [fp, #-12] 11 8410: e51b000c ldr r0, [fp, #-12] 12 8414: e51b1008 ldr r1, [fp, #-8] 13 8418: eb000003 bl 842c 14 841c: e1a00003 mov r0, r3 15 8420: e24bd004 sub sp, fp, #4 16 8424: e8bd8800 pop {fp, pc} 17 8428: 00000102 andeq r0, r0, r2, lsl #2
加入O1编译
1 00008430: 2 8430: e52de004 push {lr} ; (str lr, [sp, #-4]!) 3 8434: e24dd00c sub sp, sp, #12 4 8438: e59f0010 ldr r0, [pc, #16] ; 8450 5 843c: e58d0004 str r0, [sp, #4] 6 8440: e59d1004 ldr r1, [sp, #4] 7 8444: ebffffe8 bl 83ec 8 8448: e28dd00c add sp, sp, #12 9 844c: e49df004 pop {pc} ; (ldr pc, [sp], #4) 10 8450: 00000102 andeq r0, r0, r2, lsl #2
可见此时R0与R1的来源不同,R0(a)的值是将i入栈时候的值,如果此时(在ldr之后)多线程修改该值即入参就出错了(当然在这个例子中不可能,因值是全局初始化的在代码段里,但意思是这个意思),而R1仍从堆栈里取该值保证变量的正确性
结论:
1. 由此可见内核使用*(volatile typeof(x) *)(&x)的方式是必要的并且简化版((volatile typeof(x)(x))并没有起到意想中的效果(因为x是做为取出的值去做强制类型转换,而当它转换时可能已经在寄存器里了)
2. 理解这类问题最好方法就是写个demo直接反汇编,一切都清楚了