Компилятор не знает о кешировании, это не кеширование, это говорит компилятору, что значение может меняться между доступами. Таким образом, чтобы функционально реализовать наш код, он должен выполнять обращения, которые мы запрашиваем, в том порядке, в котором мы их запрашиваем. Не могу оптимизировать.
void fun1 ( void )
{
/* volatile */ int lock = 999;
while (lock) continue;
}
void fun2 ( void )
{
volatile int lock = 999;
while (lock) continue;
}
volatile int vlock;
int ulock;
void fun3 ( void )
{
while(vlock) continue;
}
void fun4 ( void )
{
while(ulock) continue;
}
void fun5 ( void )
{
vlock=3;
vlock=4;
}
void fun6 ( void )
{
ulock=3;
ulock=4;
}
Мне легче видеть на руке ... не имеет значения.
Disassembly of section .text:
00001000 <fun1>:
1000: eafffffe b 1000 <fun1>
00001004 <fun2>:
1004: e59f3018 ldr r3, [pc, #24] ; 1024 <fun2+0x20>
1008: e24dd008 sub sp, sp, #8
100c: e58d3004 str r3, [sp, #4]
1010: e59d3004 ldr r3, [sp, #4]
1014: e3530000 cmp r3, #0
1018: 1afffffc bne 1010 <fun2+0xc>
101c: e28dd008 add sp, sp, #8
1020: e12fff1e bx lr
1024: 000003e7 andeq r0, r0, r7, ror #7
00001028 <fun3>:
1028: e59f200c ldr r2, [pc, #12] ; 103c <fun3+0x14>
102c: e5923000 ldr r3, [r2]
1030: e3530000 cmp r3, #0
1034: 012fff1e bxeq lr
1038: eafffffb b 102c <fun3+0x4>
103c: 00002000
00001040 <fun4>:
1040: e59f3014 ldr r3, [pc, #20] ; 105c <fun4+0x1c>
1044: e5933000 ldr r3, [r3]
1048: e3530000 cmp r3, #0
104c: 012fff1e bxeq lr
1050: e3530000 cmp r3, #0
1054: 012fff1e bxeq lr
1058: eafffffa b 1048 <fun4+0x8>
105c: 00002004
00001060 <fun5>:
1060: e3a01003 mov r1, #3
1064: e3a02004 mov r2, #4
1068: e59f3008 ldr r3, [pc, #8] ; 1078 <fun5+0x18>
106c: e5831000 str r1, [r3]
1070: e5832000 str r2, [r3]
1074: e12fff1e bx lr
1078: 00002000
0000107c <fun6>:
107c: e3a02004 mov r2, #4
1080: e59f3004 ldr r3, [pc, #4] ; 108c <fun6+0x10>
1084: e5832000 str r2, [r3]
1088: e12fff1e bx lr
108c: 00002004
Disassembly of section .bss:
00002000 <vlock>:
2000: 00000000
00002004 <ulock>:
2004: 00000000
Первый - самый красноречивый.
00001000 <fun1>:
1000: eafffffe b 1000 <fun1>
Будучи локальной переменной, которая инициализирована и является энергонезависимой, тогда компилятор может предположить, что он не изменит значение между обращениями, поэтому он никогда не изменится в цикле while, так что по сути это цикл while 1. Если бы начальное значение было нулевым, это было бы простым возвратом, поскольку оно никогда не может быть ненулевым, будучи неустойчивым.
fun2 - это локальная переменная, которую необходимо построить фрейм стека
Он делает то, что предполагается, что код пытался сделать, подождать этой разделяемой переменной, которая может измениться во время цикла
1010: e59d3004 ldr r3, [sp, #4]
1014: e3530000 cmp r3, #0
1018: 1afffffc bne 1010 <fun2+0xc>
так что он производит выборку и каждый раз проверяет, что он делает, через цикл.
fun3 и fun4 одинаковы, но более реалистичны, так как внешний по отношению к коду функции не собирается менять блокировку, поскольку неглобальный не имеет большого смысла для вашего цикла while.
102c: e5923000 ldr r3, [r2]
1030: e3530000 cmp r3, #0
1034: 012fff1e bxeq lr
1038: eafffffb b 102c <fun3+0x4>
для случая volatile fun3 переменная должна быть прочитана и протестирована в каждом цикле
1044: e5933000 ldr r3, [r3]
1048: e3530000 cmp r3, #0
104c: 012fff1e bxeq lr
1050: e3530000 cmp r3, #0
1054: 012fff1e bxeq lr
1058: eafffffa b 1048 <fun4+0x8>
для того, чтобы энергонезависимое являлось глобальным, оно должно попробовать его один раз, очень интересно, что здесь сделал компилятор, подумать о том, почему он это сделал, но в любом случае вы можете видеть, что «цикл» повторно проверяет прочитанное значение хранится в регистре (не кэшируется), который никогда не изменится с помощью правильной программы. Функционально мы попросили его прочитать переменную только один раз, используя энергонезависимую, а затем проверять это значение бесконечно.
fun5 и fun6 также демонстрируют, что volatile требует, чтобы компилятор выполнил доступ к переменной в своем хранилище, прежде чем перейти к следующей операции / доступу в коде. Поэтому, когда volatile, мы просим компилятор выполнить два назначения, два хранилища. Когда энергонезависимый компилятор может оптимизировать первое хранилище и делать только последнее, как если бы вы смотрели на код в целом, эта функция (fun6) оставляет переменную равной 4, поэтому функция оставляет переменную равной 4.
Решение для x86 не менее интересно, repz retq повсюду (с компилятором на моем компьютере), не трудно понять, что это такое.
Ни бэкенды aarch64, x86, mips, riscv, msp430, pdp11, ни двойная проверка fun3 () не выполняются.
pdp11 на самом деле проще для чтения кода (нет ничего удивительного)
00000000 <_fun1>:
0: 01ff br 0 <_fun1>
00000002 <_fun2>:
2: 65c6 fffe add $-2, sp
6: 15ce 03e7 mov $1747, (sp)
a: 1380 mov (sp), r0
c: 02fe bne a <_fun2+0x8>
e: 65c6 0002 add $2, sp
12: 0087 rts pc
00000014 <_fun3>:
14: 1dc0 0026 mov $3e <_vlock>, r0
18: 02fd bne 14 <_fun3>
1a: 0087 rts pc
0000001c <_fun4>:
1c: 1dc0 001c mov $3c <_ulock>, r0
20: 0bc0 tst r0
22: 02fe bne 20 <_fun4+0x4>
24: 0087 rts pc
00000026 <_fun5>:
26: 15f7 0003 0012 mov $3, $3e <_vlock>
2c: 15f7 0004 000c mov $4, $3e <_vlock>
32: 0087 rts pc
00000034 <_fun6>:
34: 15f7 0004 0002 mov $4, $3c <_ulock>
3a: 0087 rts pc
(это не связанная версия)
cmp DWORD PTR [RSP - 8], 0. <--- Почему сравнение выполняется с 0, в то время как DWORD PTR [RSP - 8] содержит 999? </p>
Хотя истинное ложное сравнение означает, что оно равно нулю или не равно нулю
Почему DWORD PTR [RSP - 8] копируется в EAX и опять же, почему выполняется сравнение между 0 и EAX?
mov -0x8(%rsp),%eax
cmp 0,%eax
cmp 0,-0x8(%rsp)
as so.s -o so.o
so.s: Assembler messages:
so.s:3: Error: too many memory references for `cmp'
сравнить хочет зарегистрироваться. Таким образом, он считывает данные в регистр, поэтому может выполнять сравнение, поскольку не может выполнить сравнение между непосредственным доступом и доступом к памяти в одной инструкции. Если бы они могли сделать это в одной инструкции, они бы сделали.