Using Wokwi is an easy way to get the assembly output. Without volatile
Serial.printf("flag = %d \r\n", net_flag);
400d15b6: fa9b81 l32r a8, 400d0024 <_stext+0x4> (3ffbdb60 <net_flag>)
400d15b9: fa9bb1 l32r a11, 400d0028 <_stext+0x8> (3f400120 <_flash_rodata_start>)
400d15bc: 0008c2 l8ui a12, a8, 0
400d15bf: 07ad mov.n a10, a7
400d15c1: 0057a5 call8 400d1b3c <_ZN5Print6printfEPKcz>
while (net_flag);
Serial.printf("flag = %d \r\n", net_flag);
400d15c4: fa99b1 l32r a11, 400d0028 <_stext+0x8> (3f400120 <_flash_rodata_start>)
400d15c7: 0c0c movi.n a12, 0
400d15c9: 07ad mov.n a10, a7
400d15cb: 005725 call8 400d1b3c <_ZN5Print6printfEPKcz>
I don't know ESP32 assembly, but I'd guess the compiler is out-smarting you and immediately setting the bool to false (in a12, with no loop) so that it can print what you asked for. It cannot be otherwise. In contrast, with volatile
400d15c6: 0058e5 call8 400d1b54 <_ZN5Print6printfEPKcz>
while (net_flag);
400d15c9: 0020c0 memw
400d15cc: 000682 l8ui a8, a6, 0
400d15cf: 748080 extui a8, a8, 0, 8
400d15d2: ff3856 bnez a8, 400d15c9 <_Z5setupv+0x45>
Serial.printf("flag = %d \r\n", net_flag);
400d15d5: 0020c0 memw
400d15d8: 0006c2 l8ui a12, a6, 0
400d15db: fa93b1 l32r a11, 400d0028 <_stext+0x8> (3f400120 <_flash_rodata_start>)
400d15de: 74c0c0 extui a12, a12, 0, 8
400d15e1: 07ad mov.n a10, a7
400d15e3: 005725 call8 400d1b54 <_ZN5Print6printfEPKcz>
the loop code is generated (bnez -> "branch"-something), because as volatile, some other thread might change the variable in a way that can be seen. Unlike before, the code is potentially valid.