อะไรvolatile
คำหลักทำอย่างไร ใน C ++ มันแก้ปัญหาอะไรได้บ้าง?
ในกรณีของฉันฉันไม่เคยต้องการมันอย่างรู้เท่าทัน
volatile
สามารถใช้งานได้อย่างมีประสิทธิภาพใส่กันในแง่คนธรรมดาสวย Link: สิ่งพิมพ์.
volatile
มีประโยชน์มากกว่าfriend
คำหลัก
อะไรvolatile
คำหลักทำอย่างไร ใน C ++ มันแก้ปัญหาอะไรได้บ้าง?
ในกรณีของฉันฉันไม่เคยต้องการมันอย่างรู้เท่าทัน
volatile
สามารถใช้งานได้อย่างมีประสิทธิภาพใส่กันในแง่คนธรรมดาสวย Link: สิ่งพิมพ์.
volatile
มีประโยชน์มากกว่าfriend
คำหลัก
คำตอบ:
volatile
จำเป็นถ้าคุณกำลังอ่านจากจุดในหน่วยความจำที่พูดกระบวนการแยก / อุปกรณ์ / สิ่งที่อาจเขียน
ฉันเคยทำงานกับ RAM แบบดูอัลพอร์ตในระบบมัลติโพรเซสเซอร์เป็นเส้นตรง C เราใช้ฮาร์ดแวร์ที่จัดการค่า 16 บิตเป็นเซมาฟอร์เพื่อรู้ว่าเมื่อไรที่คนอื่นทำเสร็จ เป็นหลักเราทำสิ่งนี้:
void waitForSemaphore()
{
volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}
หากไม่มีvolatile
เครื่องมือเพิ่มประสิทธิภาพจะเห็นว่าลูปไร้ประโยชน์ (คนที่แต่งตัวประหลาดไม่เคยกำหนดค่า! เขาถั่วกำจัดรหัสนั้น!) และรหัสของฉันจะดำเนินการต่อโดยไม่ได้รับสัญญาณส่งสัญญาณทำให้เกิดปัญหาในภายหลัง
uint16_t* volatile semPtr
เขียนแทน สิ่งนี้ควรทำเครื่องหมายตัวชี้ว่าระเหย (แทนที่จะเป็นค่าที่ชี้ไปที่) เพื่อตรวจสอบตัวชี้เช่นsemPtr == SOME_ADDR
อาจไม่ได้รับการปรับให้เหมาะสม อย่างไรก็ตามนี่ก็หมายถึงค่าของความผันผวนที่ชี้ชัดอีกครั้งเช่นกัน ไม่มี?
volatile
จำเป็นสำหรับการพัฒนาระบบฝังตัวหรือไดรเวอร์อุปกรณ์ซึ่งคุณต้องอ่านหรือเขียนอุปกรณ์ฮาร์ดแวร์ที่แมปหน่วยความจำ เนื้อหาของการลงทะเบียนอุปกรณ์เฉพาะสามารถเปลี่ยนแปลงได้ตลอดเวลาดังนั้นคุณต้องใช้volatile
คำหลักเพื่อให้แน่ใจว่าคอมไพเลอร์ไม่สามารถเข้าถึงการเข้าถึงดังกล่าวได้
โปรเซสเซอร์บางตัวมีการลงทะเบียนจุดลอยตัวที่มีความแม่นยำมากกว่า 64 บิต (เช่น 32- บิต x86 ที่ไม่มี SSE ดูความคิดเห็นของปีเตอร์) ด้วยวิธีนี้ถ้าคุณใช้การดำเนินการหลายอย่างกับตัวเลขที่มีความแม่นยำสองเท่าคุณจะได้คำตอบที่แม่นยำกว่าจริง ๆ ถ้าคุณจะตัดทอนผลลัพธ์ระหว่างกลางให้เหลือ 64 บิต
นี่เป็นเรื่องปกติ แต่ก็หมายความว่าขึ้นอยู่กับวิธีที่คอมไพเลอร์มอบหมายการลงทะเบียนและทำการปรับให้เหมาะสมคุณจะได้ผลลัพธ์ที่แตกต่างกันสำหรับการดำเนินการเดียวกันในอินพุตเดียวกัน หากคุณต้องการความสอดคล้องคุณสามารถบังคับให้แต่ละการดำเนินการกลับไปที่หน่วยความจำโดยใช้คำสำคัญระเหย
นอกจากนี้ยังมีประโยชน์สำหรับอัลกอริทึมบางอย่างที่ไม่สมเหตุสมผลเกี่ยวกับพีชคณิต แต่ลดข้อผิดพลาดของจุดลอยเช่น Kahan summation พีชคณิตมันเป็น nop ดังนั้นมันมักจะได้รับการปรับปรุงอย่างไม่ถูกต้องเว้นแต่ตัวแปรกลางบางอย่างจะระเหย
volatile double
แทนที่จะเป็นเพียงแค่double
เพื่อให้แน่ใจว่าถูกตัดทอนจากความแม่นยำของ FPU เป็นความแม่นยำ 64 บิต (RAM) ก่อนดำเนินการคำนวณต่อไป ผลลัพธ์มีความแตกต่างอย่างมีนัยสำคัญเนื่องจากมีการพูดเกินจริงเพิ่มเติมของข้อผิดพลาดจุดลอยตัว
g++ -mfpmath=sse
จะใช้สำหรับ x86 แบบ 32 บิตเช่นกัน คุณสามารถใช้gcc -ffloat-store
บังคับให้ปัดเศษได้ทุกที่แม้ใช้ x87 หรือคุณสามารถตั้งค่าความแม่นยำ x87 เป็น mantissa 53 บิต: randomascii.wordpress.com/2012/03/21/ …
volatile
บังคับให้ปัดเศษในบางสถานที่โดยไม่สูญเสียผลประโยชน์ในทุกที่
จากบทความ"ระเหยเป็นสัญญา"โดยแดน Saks:
(... ) วัตถุที่ระเหยได้ซึ่งเป็นค่าที่อาจเปลี่ยนแปลงได้เอง นั่นคือเมื่อคุณประกาศให้วัตถุมีความผันผวนคุณกำลังบอกคอมไพเลอร์ว่าวัตถุนั้นอาจเปลี่ยนสถานะแม้ว่าจะไม่มีคำสั่งในโปรแกรมปรากฏให้เปลี่ยนก็ตาม
นี่คือลิงค์ไปยังสามบทความของเขาเกี่ยวกับvolatile
คำหลัก:
คุณต้องใช้สารระเหยเมื่อใช้โครงสร้างข้อมูลที่ไม่มีล็อค มิฉะนั้นคอมไพเลอร์มีอิสระในการเพิ่มประสิทธิภาพการเข้าถึงตัวแปรซึ่งจะเปลี่ยนความหมาย
หากต้องการกล่าวอีกวิธีหนึ่ง volatile จะบอกคอมไพเลอร์ว่าการเข้าถึงตัวแปรนี้จะต้องสอดคล้องกับการดำเนินการอ่าน / เขียนหน่วยความจำกายภาพ
ตัวอย่างเช่นนี่คือวิธีการประกาศ InterlockedIncrement ใน Win32 API:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
std::atomic<LONG>
เพื่อให้คุณสามารถเขียนรหัส lockless ได้อย่างปลอดภัยมากขึ้นโดยไม่มีปัญหาในการโหลดแท้ / ร้านค้าแท้ที่ได้รับการปรับให้เหมาะสม
แอปพลิเคชันขนาดใหญ่ที่ฉันใช้ในการทำงานในต้นปี 1990 มีการจัดการข้อยกเว้นแบบอิง C โดยใช้ setjmp และ longjmp คำสำคัญระเหยมีความจำเป็นกับตัวแปรที่มีค่าที่จำเป็นต้องเก็บรักษาไว้ในบล็อกของรหัสที่ทำหน้าที่เป็นประโยค "จับ" เพื่อมิให้ vars เหล่านั้นถูกเก็บไว้ในรีจิสเตอร์และลบออกโดย longjmp
ในมาตรฐาน C หนึ่งในสถานที่ที่ใช้volatile
คือพร้อมตัวจัดการสัญญาณ ในความเป็นจริงในมาตรฐาน C สิ่งที่คุณสามารถทำได้อย่างปลอดภัยในตัวจัดการสัญญาณคือปรับเปลี่ยนvolatile sig_atomic_t
ตัวแปรหรือออกอย่างรวดเร็ว แท้จริงแล้ว AFAIK เป็นสถานที่แห่งเดียวในมาตรฐาน C ที่volatile
จำเป็นต้องใช้เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนด
ISO / IEC 9899: 2011 §7.14.1.1
signal
ฟังก์ชั่น¶5หากสัญญาณเกิดขึ้นนอกเหนือจากที่เป็นผลมาจากการเรียก
abort
หรือraise
ฟังก์ชั่นพฤติกรรมจะไม่ได้กำหนดถ้าตัวจัดการสัญญาณหมายถึงวัตถุใด ๆ ที่มีระยะเวลาการจัดเก็บแบบคงที่หรือด้ายที่ไม่ได้ล็อคอะตอมวัตถุอื่นที่ไม่ใช่ล็อค ไปยังวัตถุที่ประกาศว่าเป็นvolatile sig_atomic_t
, หรือตัวจัดการสัญญาณเรียกใช้ฟังก์ชันใด ๆ ในไลบรารีมาตรฐานอื่นนอกเหนือจากabort
ฟังก์ชัน,_Exit
ฟังก์ชั่น,quick_exit
ฟังก์ชั่นหรือsignal
ฟังก์ชั่นที่มีอาร์กิวเมนต์แรกเท่ากับจำนวนสัญญาณที่สอดคล้องกับสัญญาณที่ก่อให้เกิดการร้องขอ ผู้ดำเนินการ นอกจากนี้หากการเรียกใช้signal
ฟังก์ชันส่งผลให้ SIG_ERR ส่งคืนค่าของerrno
ไม่แน่นอน 252)252)หากสัญญาณใด ๆ ถูกสร้างขึ้นโดยตัวจัดการสัญญาณแบบอะซิงโครนัสพฤติกรรมจะไม่ได้กำหนด
นั่นหมายความว่าใน Standard C คุณสามารถเขียน:
static volatile sig_atomic_t sig_num = 0;
static void sig_handler(int signum)
{
signal(signum, sig_handler);
sig_num = signum;
}
และไม่มาก
POSIX มีความผ่อนปรนมากขึ้นเกี่ยวกับสิ่งที่คุณสามารถทำได้ในตัวจัดการสัญญาณ แต่ยังมีข้อ จำกัด (และหนึ่งในข้อ จำกัด คือว่าไลบรารี I / O มาตรฐาน - printf()
et al - ไม่สามารถใช้ได้อย่างปลอดภัย)
การพัฒนาสำหรับการฝังตัวฉันมีการวนรอบที่ตรวจสอบตัวแปรที่สามารถเปลี่ยนแปลงได้ในตัวจัดการขัดจังหวะ หากไม่มี "volatile" loop จะกลายเป็น noop เท่าที่คอมไพเลอร์สามารถบอกได้ว่าตัวแปรจะไม่เปลี่ยนแปลงดังนั้นจึงปรับการตรวจสอบให้เหมาะสม
สิ่งเดียวกันจะนำไปใช้กับตัวแปรที่อาจมีการเปลี่ยนแปลงในเธรดที่แตกต่างกันในสภาพแวดล้อมแบบดั้งเดิมมากขึ้น แต่ที่นั่นเรามักจะเรียกการซิงโครไนซ์ดังนั้นคอมไพเลอร์ไม่ได้ฟรีด้วยการเพิ่มประสิทธิภาพ
ฉันใช้มันใน debug builds เมื่อคอมไพเลอร์ยืนยันในการปรับตัวแปรที่ฉันต้องการให้สามารถดูได้ในขณะที่ฉันใช้รหัส
นอกเหนือจากการใช้ตามที่ตั้งใจไว้แล้วสารระเหยจะใช้ใน (เทมเพลต) การเปรียบเทียบโปรแกรม สามารถใช้เพื่อป้องกันการบรรทุกเกินพิกัดโดยไม่ตั้งใจเนื่องจากแอตทริบิวต์การระเหย (เช่น const) มีส่วนร่วมในการแก้ปัญหาการโอเวอร์โหลด
template <typename T>
class Foo {
std::enable_if_t<sizeof(T)==4, void> f(T& t)
{ std::cout << 1 << t; }
void f(T volatile& t)
{ std::cout << 2 << const_cast<T&>(t); }
void bar() { T t; f(t); }
};
สิ่งนี้ถูกกฎหมาย โอเวอร์โหลดทั้งสองสามารถเรียกได้และเกือบจะเหมือนกัน โยนในvolatile
การโอเวอร์โหลดเป็นกฎหมายที่เรารู้ว่าบาร์จะไม่ผ่านไม่ระเหยT
อยู่แล้ว volatile
รุ่นเป็นอย่างเคร่งครัดแย่ลง แต่ไม่เคยได้รับการแต่งตั้งในความละเอียดเกินถ้าไม่ระเหยf
สามารถใช้ได้
โปรดทราบว่ารหัสไม่เคยขึ้นอยู่กับvolatile
การเข้าถึงหน่วยความจำ
volatile
คำหลักที่มีจุดมุ่งหมายเพื่อป้องกันไม่ให้คอมไพเลอร์จากการใช้ optimisations ใด ๆ บนวัตถุที่สามารถเปลี่ยนไปในทางที่ไม่สามารถระบุได้โดยคอมไพเลอร์
วัตถุที่ถูกประกาศว่าvolatile
ถูกละเว้นจากการปรับให้เหมาะสมเนื่องจากค่าสามารถเปลี่ยนแปลงได้โดยรหัสที่อยู่นอกขอบเขตของรหัสปัจจุบันเมื่อใดก็ได้ ระบบจะอ่านค่าปัจจุบันของvolatile
วัตถุจากตำแหน่งหน่วยความจำแทนที่จะเก็บค่าไว้ในการลงทะเบียนชั่วคราว ณ จุดที่ร้องขอแม้ว่าคำสั่งก่อนหน้านี้จะขอค่าจากวัตถุเดียวกันก็ตาม
พิจารณากรณีต่อไปนี้
1) ตัวแปรโกลบอลที่แก้ไขโดยรูทีนเซอร์วิสขัดจังหวะนอกขอบเขต
2) ตัวแปรโกลบอลภายในแอ็พพลิเคชันแบบมัลติเธรด
หากเราไม่ใช้ตัวระบุแบบระเหยได้ปัญหาต่อไปนี้อาจเกิดขึ้น
1) รหัสอาจไม่ทำงานตามที่คาดไว้เมื่อเปิดใช้งานการเพิ่มประสิทธิภาพ
2) รหัสอาจไม่ทำงานอย่างที่คาดไว้เมื่อมีการเปิดใช้งานและใช้งานอินเตอร์รัปต์
ระเหย: เพื่อนที่ดีที่สุดของโปรแกรมเมอร์
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
นอกเหนือจากข้อเท็จจริงที่ว่าคำสำคัญระเหยใช้สำหรับบอกคอมไพเลอร์ไม่ปรับการเข้าถึงตัวแปรบางอย่าง (ที่สามารถแก้ไขได้โดยเธรดหรือรูทีนการขัดจังหวะ), มันสามารถใช้เพื่อลบบั๊กคอมไพเลอร์ด้วย - มันสามารถ เป็น ---
ตัวอย่างเช่นฉันทำงานบนแพลตฟอร์มที่ฝังตัวคอมไพเลอร์ได้ทำการโจมตีที่ผิดเกี่ยวกับค่าของตัวแปร หากรหัสไม่ได้รับการปรับให้เหมาะสมโปรแกรมจะทำงานได้ ด้วยการปรับให้เหมาะสม (ซึ่งจำเป็นจริงๆเพราะเป็นกิจวัตรที่สำคัญ) รหัสจะทำงานไม่ถูกต้อง ทางออกเดียว (แต่ไม่ถูกต้องมาก) คือการประกาศตัวแปร 'ผิดปกติ' ว่าเป็นสารระเหย
ดูเหมือนว่าโปรแกรมของคุณจะทำงานแม้ไม่มีvolatile
คำหลักใช่ไหม บางทีนี่อาจเป็นเหตุผล:
ดังที่ได้กล่าวไว้ก่อนหน้านี้volatile
คำหลักช่วยสำหรับกรณีเช่นนี้
volatile int* p = ...; // point to some memory
while( *p!=0 ) {} // loop until the memory becomes zero
แต่ดูเหมือนว่าแทบจะไม่มีผลกระทบเลยเมื่อมีการเรียกใช้ฟังก์ชันภายนอกหรือไม่ใช่แบบอินไลน์ เช่น:
while( *p!=0 ) { g(); }
จากนั้นมีหรือไม่มีvolatile
ผลลัพธ์เดียวกันเกือบจะถูกสร้างขึ้น
ตราบใดที่ g () สามารถอินไลน์ได้อย่างสมบูรณ์คอมไพเลอร์สามารถมองเห็นทุกสิ่งที่เกิดขึ้นและสามารถปรับให้เหมาะสม แต่เมื่อโปรแกรมทำการเรียกไปยังสถานที่ที่คอมไพเลอร์ไม่สามารถมองเห็นสิ่งที่เกิดขึ้นมันจะไม่ปลอดภัยสำหรับคอมไพเลอร์ที่จะทำให้สมมติฐานใด ๆ อีกต่อไป ดังนั้นคอมไพเลอร์จะสร้างรหัสที่อ่านจากหน่วยความจำโดยตรง
แต่ระวังของวันเมื่อฟังก์ชั่นของคุณ g () กลายเป็นแบบอินไลน์ (ทั้งเนื่องจากการเปลี่ยนแปลงที่ชัดเจนหรือเนื่องจากความฉลาดคอมไพเลอร์ / linker) จากนั้นรหัสของคุณอาจแตกถ้าคุณลืมvolatile
คำหลัก!
ดังนั้นฉันแนะนำให้เพิ่มvolatile
คำหลักแม้ว่าโปรแกรมของคุณจะทำงานไม่ได้ มันทำให้ความตั้งใจชัดเจนและแข็งแกร่งขึ้นเกี่ยวกับการเปลี่ยนแปลงในอนาคต
volatile
ตัวชี้ฟังก์ชั่นที่มีคุณภาพ:void (* volatile fun_ptr)() = fun; fun_ptr();
ในวันแรกของ C คอมไพเลอร์จะตีความการกระทำทั้งหมดที่อ่านและเขียน lvalues เป็นการดำเนินการของหน่วยความจำที่จะดำเนินการในลำดับเดียวกันกับการอ่านและการเขียนที่ปรากฏในรหัส ประสิทธิภาพอาจเพิ่มขึ้นอย่างมากในหลาย ๆ กรณีหากคอมไพเลอร์ได้รับอิสรภาพจำนวนหนึ่งเพื่อสั่งซื้อใหม่และรวมการดำเนินงาน แต่มีปัญหากับสิ่งนี้ แม้แต่การดำเนินการก็มักจะถูกระบุไว้ในลำดับที่แน่นอนเพียงเพราะมันจำเป็นต้องระบุในการสั่งซื้อบางอย่างและทำให้โปรแกรมเมอร์เลือกหนึ่งในทางเลือกที่ดีเท่าเทียมกันหลายอย่างที่ไม่ได้เสมอ บางครั้งมันเป็นสิ่งสำคัญที่การดำเนินการบางอย่างเกิดขึ้นในลำดับเฉพาะ
รายละเอียดการเรียงลำดับที่สำคัญจะแตกต่างกันไปตามแพลตฟอร์มเป้าหมายและฟิลด์แอปพลิเคชัน แทนที่จะให้การควบคุมที่มีรายละเอียดเป็นพิเศษมาตรฐานเลือกใช้รูปแบบง่าย ๆ : หากลำดับการเข้าถึงเสร็จสิ้นด้วย lvalues ที่ไม่ผ่านการรับรองvolatile
ผู้รวบรวมอาจเรียงลำดับใหม่และรวมเข้าด้วยกันตามที่เห็นสมควร หากการดำเนินการเสร็จสิ้นด้วยvolatile
-valvalable การใช้งานที่มีคุณภาพควรเสนอการรับประกันการสั่งซื้อเพิ่มเติมใด ๆ ที่อาจจำเป็นต้องใช้โดยรหัสที่กำหนดเป้าหมายแพลตฟอร์มและแอปพลิเคชันที่ต้องการโดยไม่ต้องใช้ไวยากรณ์ที่ไม่ได้มาตรฐาน
น่าเสียดายที่แทนที่จะระบุว่าผู้ค้ำประกันต้องการโปรแกรมเมอร์อะไรคอมไพเลอร์หลายคนเลือกที่จะเสนอการรับประกันขั้นต่ำเปล่าที่ได้รับคำสั่งจากมาตรฐาน สิ่งนี้ทำให้volatile
มีประโยชน์น้อยกว่าที่ควรจะเป็น บน gcc หรือเสียงดังกราวด์โปรแกรมเมอร์จำเป็นต้องใช้ mutex พื้นฐาน "hand-off mutex" [ที่งานที่ได้รับและเผยแพร่ mutex จะไม่ทำเช่นนั้นจนกว่างานอื่นจะทำ] ต้องทำ สี่สิ่ง:
ใส่การได้มาและการเปิดตัวของ mutex ในฟังก์ชั่นที่คอมไพเลอร์ไม่สามารถอินไลน์ได้และไม่สามารถใช้การปรับให้เหมาะสมทั้งโปรแกรมได้
ปรับคุณสมบัติของวัตถุทั้งหมดที่มีการปกป้องโดย mutex เป็น - volatile
สิ่งที่ไม่ควรจำเป็นหากการเข้าถึงทั้งหมดเกิดขึ้นหลังจากได้รับ mutex และก่อนที่จะปล่อยมัน
ใช้เพิ่มประสิทธิภาพระดับ 0 ถึงบังคับให้คอมไพเลอร์เพื่อสร้างรหัสราวกับว่าวัตถุทั้งหมดที่มีคุณสมบัติไม่เป็นregister
volatile
ใช้คำสั่งเฉพาะ gcc
ในทางตรงกันข้ามเมื่อใช้คอมไพเลอร์คุณภาพสูงซึ่งเหมาะสำหรับการเขียนโปรแกรมระบบเช่น icc จะมีตัวเลือกอื่น:
volatile
เขียน -qualified ได้รับการดำเนินการทุกที่ที่จำเป็นต้องได้รับหรือปล่อยการได้รับ "มือปิด mutex" ขั้นพื้นฐานต้องมีการvolatile
อ่าน (เพื่อดูว่ามันพร้อม) และไม่ควรต้องมีการvolatile
เขียนเช่นกัน (ด้านอื่น ๆ จะไม่พยายามที่จะได้รับมันอีกครั้งจนกว่ามันจะถูกส่งกลับ) แต่ต้อง ดำเนินการvolatile
เขียนที่ไม่มีความหมายยังดีกว่าตัวเลือกใด ๆ ที่มีอยู่ภายใต้ gcc หรือเสียงดังกราว
การใช้งานครั้งเดียวที่ฉันควรเตือนคุณคือในฟังก์ชั่นตัวจัดการสัญญาณหากคุณต้องการเข้าถึง / แก้ไขตัวแปรกลาง (เช่นทำเครื่องหมายเป็น exit = true) คุณต้องประกาศตัวแปรนั้นว่า 'ระเหย'
คำตอบทั้งหมดนั้นยอดเยี่ยม แต่ยิ่งไปกว่านั้นฉันต้องการแบ่งปันตัวอย่าง
ด้านล่างเป็นโปรแกรม cpp เล็กน้อย:
#include <iostream>
int x;
int main(){
char buf[50];
x = 8;
if(x == 8)
printf("x is 8\n");
else
sprintf(buf, "x is not 8\n");
x=1000;
while(x > 5)
x--;
return 0;
}
ตอนนี้ให้สร้างชุดประกอบของโค้ดด้านบน (และฉันจะวางเฉพาะส่วนของชุดประกอบที่เกี่ยวข้องที่นี่):
คำสั่งเพื่อสร้างชุดประกอบ:
g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp
และการชุมนุม:
main:
.LFB1594:
subq $40, %rsp #,
.seh_stackalloc 40
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip), %rcx #,
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0 #
# assembly.cpp:18: }
xorl %eax, %eax #
movl $5, x(%rip) #, x
addq $40, %rsp #,
ret
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
คุณสามารถเห็นได้ในแอสเซมบลีที่ไม่ได้สร้างรหัสแอสเซมบลีsprintf
เนื่องจากคอมไพเลอร์สันนิษฐานว่าx
จะไม่เปลี่ยนแปลงภายนอกของโปรแกรม และเป็นกรณีเดียวกันกับwhile
วง while
ห่วงถูกลบออกไปโดยสิ้นเชิงเนื่องจากการเพิ่มประสิทธิภาพของคอมไพเลอร์เพราะเห็นว่ามันเป็นรหัสที่ไร้ประโยชน์และทำให้ได้รับมอบหมายโดยตรง5
ไปx
(ดูmovl $5, x(%rip)
)
ปัญหาเกิดขึ้นเมื่อสิ่งที่ถ้ากระบวนการภายนอก / ฮาร์ดแวร์จะเปลี่ยนค่าของx
บางระหว่างและx = 8;
if(x == 8)
เราคาดว่าการelse
บล็อกจะทำงาน แต่น่าเสียดายที่ผู้รวบรวมได้ตัดส่วนนั้นออก
ตอนนี้เพื่อที่จะแก้ปัญหานี้ในassembly.cpp
ให้เราเปลี่ยนint x;
ไปvolatile int x;
ได้อย่างรวดเร็วและเห็นรหัสการชุมนุมที่สร้าง:
main:
.LFB1594:
subq $104, %rsp #,
.seh_stackalloc 104
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:9: if(x == 8)
movl x(%rip), %eax # x, x.1_1
# assembly.cpp:9: if(x == 8)
cmpl $8, %eax #, x.1_1
je .L11 #,
# assembly.cpp:12: sprintf(buf, "x is not 8\n");
leaq 32(%rsp), %rcx #, tmp93
leaq .LC0(%rip), %rdx #,
call _ZL7sprintfPcPKcz.constprop.0 #
.L7:
# assembly.cpp:14: x=1000;
movl $1000, x(%rip) #, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_15
cmpl $5, %eax #, x.3_15
jle .L8 #,
.p2align 4,,10
.L9:
# assembly.cpp:16: x--;
movl x(%rip), %eax # x, x.4_3
subl $1, %eax #, _4
movl %eax, x(%rip) # _4, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_2
cmpl $5, %eax #, x.3_2
jg .L9 #,
.L8:
# assembly.cpp:18: }
xorl %eax, %eax #
addq $104, %rsp #,
ret
.L11:
# assembly.cpp:10: printf("x is 8\n");
leaq .LC1(%rip), %rcx #,
call _ZL6printfPKcz.constprop.1 #
jmp .L7 #
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
ที่นี่คุณสามารถเห็นได้ว่ารหัสประกอบsprintf
, printf
และwhile
ห่วงถูกสร้างขึ้น ข้อได้เปรียบคือถ้าx
ตัวแปรถูกเปลี่ยนแปลงโดยบางโปรแกรมหรือฮาร์ดแวร์ภายนอกบางsprintf
ส่วนของรหัสจะถูกดำเนินการ และwhile
วนซ้ำในทำนองเดียวกันสามารถใช้สำหรับการรอไม่ว่างในขณะนี้
คำตอบอื่น ๆ แล้วพูดถึงการหลีกเลี่ยงการเพิ่มประสิทธิภาพเพื่อ:
ความผันผวนเป็นสิ่งจำเป็นเมื่อใดก็ตามที่คุณต้องการค่าที่จะปรากฏขึ้นมาจากภายนอกและคาดเดาไม่ได้และหลีกเลี่ยงการปรับให้เหมาะสมของคอมไพเลอร์ตามค่าที่ทราบและเมื่อผลลัพธ์ไม่ได้ใช้จริง ๆ แต่คุณต้องการให้คำนวณ คุณต้องการคำนวณหลาย ๆ ครั้งเพื่อเป็นเกณฑ์มาตรฐานและคุณต้องการการคำนวณเพื่อเริ่มต้นและสิ้นสุดที่จุดที่แม่นยำ
อ่านความผันผวนเป็นเหมือนการดำเนินการป้อนข้อมูล (ชอบscanf
หรือใช้cin
): ค่าที่ดูเหมือนว่าจะมาจากด้านนอกของโปรแกรมเพื่อการคำนวณใด ๆ ที่มีการพึ่งพาความต้องการค่าที่จะเริ่มต้นหลังจากที่มัน
เขียนความผันผวนเป็นเหมือนการดำเนินการส่งออก (ชอบprintf
หรือใช้cout
): มูลค่าที่ดูเหมือนว่าจะมีการสื่อสารด้านนอกของโปรแกรมดังนั้นหากค่าขึ้นอยู่กับการคำนวณก็จะต้องมีการดำเนินการเสร็จสิ้นก่อน
ดังนั้นคู่ของความผันผวนอ่าน / เขียนสามารถใช้ในการเชื่องมาตรฐานและทำให้การวัดเวลาที่มีความหมาย
โดยไม่ต้องมีความผันผวนการคำนวณของคุณอาจจะเริ่มต้นโดยคอมไพเลอร์ก่อนเป็นไม่มีอะไรที่จะป้องกันไม่ให้เกิดการจัดเรียงใหม่ของการคำนวณที่มีฟังก์ชั่นเช่นการวัดเวลา