เหตุใดจึงต้องมีการระเหยใน C


คำตอบ:


423

Volatile บอกคอมไพเลอร์ว่าอย่าปรับแต่งสิ่งใดที่เกี่ยวข้องกับตัวแปร volatile

มีเหตุผลทั่วไปอย่างน้อยสามประการในการใช้งานทั้งหมดเกี่ยวข้องกับสถานการณ์ที่ค่าของตัวแปรสามารถเปลี่ยนแปลงได้โดยไม่ต้องดำเนินการใด ๆ จากโค้ดที่มองเห็นได้: เมื่อคุณเชื่อมต่อกับฮาร์ดแวร์ที่เปลี่ยนค่าตัวเอง เมื่อมีเธรดอื่นกำลังทำงานซึ่งใช้ตัวแปร หรือเมื่อมีตัวจัดการสัญญาณที่อาจเปลี่ยนค่าของตัวแปร

สมมติว่าคุณมีฮาร์ดแวร์ชิ้นเล็ก ๆ ที่ถูกแมปเข้ากับ RAM ที่ไหนสักแห่งและมีสองที่อยู่: พอร์ตคำสั่งและพอร์ตข้อมูล:

typedef struct
{
  int command;
  int data;
  int isbusy;
} MyHardwareGadget;

ตอนนี้คุณต้องการส่งคำสั่ง:

void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isbusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

ดูง่าย แต่อาจล้มเหลวได้เนื่องจากคอมไพเลอร์มีอิสระในการเปลี่ยนลำดับการเขียนข้อมูลและคำสั่ง สิ่งนี้จะทำให้แกดเจ็ตเล็ก ๆ ของเราออกคำสั่งด้วยค่าข้อมูลก่อนหน้า ยังดูที่การรอในขณะที่ไม่ว่าง อันนั้นจะถูกปรับให้เหมาะสม คอมไพเลอร์จะพยายามฉลาดอ่านค่าของ isbusy เพียงครั้งเดียวแล้วเข้าสู่วงวนไม่สิ้นสุด นั่นไม่ใช่สิ่งที่คุณต้องการ

วิธีที่จะหลีกเลี่ยงปัญหานี้ก็คือการประกาศให้อุปกรณ์ตัวชี้มีความผันผวน วิธีนี้คอมไพเลอร์ถูกบังคับให้ทำสิ่งที่คุณเขียน ไม่สามารถลบการมอบหมายหน่วยความจำไม่สามารถแคชตัวแปรในการลงทะเบียนและไม่สามารถเปลี่ยนลำดับของการมอบหมาย:

นี่เป็นรุ่นที่ถูกต้อง:

   void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

46
โดยส่วนตัวแล้วฉันต้องการให้ขนาดจำนวนเต็มที่ชัดเจนเช่น int8 / int16 / int32 เมื่อพูดคุยกับฮาร์ดแวร์ คำตอบที่ดีแม้ว่า;)
tonylo

22
ใช่คุณควรประกาศสิ่งต่าง ๆ ด้วยขนาดทะเบียนคงที่ แต่เฮ้ - มันเป็นเพียงตัวอย่าง
Nils Pipenbrinck

69
ความผันผวนยังจำเป็นในโค้ดเธรดเมื่อคุณเล่นกับข้อมูลที่ไม่ได้รับการป้องกันพร้อมกัน และใช่มีเวลาที่ถูกต้องที่จะทำเช่นนั้นคุณสามารถเขียนคิวคิวข้อความวงกลมที่ปลอดภัยโดยไม่ต้องมีการป้องกันการเกิดพร้อมกันชัดเจน แต่มันจะต้องมีสารระเหย
Gordon Wrigley

14
อ่านข้อมูลจำเพาะ C ยากขึ้น ความผันผวนมีเพียงพฤติกรรมที่กำหนดไว้ในอุปกรณ์ I / O ที่ถูกแมปหน่วยความจำหรือหน่วยความจำที่สัมผัสโดยฟังก์ชั่นการขัดจังหวะแบบอะซิงโครนัส มันบอกว่าไม่มีอะไรเกี่ยวกับเธรดและคอมไพเลอร์ที่ปรับการเข้าถึงหน่วยความจำที่ถูกสัมผัสโดยหลายเธรดให้เหมาะสม
ephemient

17
@ โตโยต้า: ผิดอย่างสมบูรณ์ เศร้า 17 คนไม่รู้ ระเหยไม่ได้เป็นรั้วหน่วยความจำ มันเป็นเรื่องที่เกี่ยวข้องเท่านั้นที่จะหลีกเลี่ยงการตัดออกรหัสในระหว่างการเพิ่มประสิทธิภาพอยู่บนสมมติฐานของผลข้างเคียงที่ไม่สามารถมองเห็นได้
v.oddou

187

volatileใน C จริง ๆ แล้วมีจุดประสงค์เพื่อไม่ให้แคชค่าของตัวแปรโดยอัตโนมัติ มันจะบอกคอมไพเลอร์ไม่ให้แคชค่าของตัวแปรนี้ ดังนั้นมันจะสร้างรหัสเพื่อรับค่าของvolatileตัวแปรที่กำหนดจากหน่วยความจำหลักทุกครั้งที่พบมัน กลไกนี้ใช้เพราะเมื่อใดก็ตามที่ค่าสามารถแก้ไขได้โดยระบบปฏิบัติการหรือขัดจังหวะใด ๆ ดังนั้นการใช้volatileจะช่วยให้เราเข้าถึงคุณค่าใหม่ทุกครั้ง


เข้ามาในการดำรงอยู่? ไม่ใช่ "การระเหย" ที่ยืมมาจาก C ++ หรือไม่ ดูเหมือนว่าฉันจะจำ ...
syntaxerror

นี่ไม่ใช่ความผันผวนทั้งหมดเกี่ยวกับ - นอกจากนี้ยังห้ามการเรียงลำดับใหม่บางอย่างหากระบุว่าเป็นความผันผวน ..
FaceBro

4
@FaceBro: จุดประสงค์ของvolatileการทำให้มันเป็นไปได้สำหรับคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพรหัสในขณะที่ยังช่วยให้โปรแกรมเมอร์เพื่อให้บรรลุความหมายที่จะประสบความสำเร็จโดยไม่ต้องเพิ่มประสิทธิภาพดังกล่าว ผู้เขียนมาตรฐานคาดว่าการใช้งานที่มีคุณภาพจะรองรับความหมายอะไรก็ตามที่มีประโยชน์เนื่องจากแพลตฟอร์มเป้าหมายและฟิลด์แอปพลิเคชันและไม่คาดหวังว่าผู้เขียนคอมไพเลอร์จะพยายามเสนอความหมายคุณภาพต่ำสุดที่เป็นไปตามมาตรฐานและไม่ 100% โง่ (โปรดทราบว่าผู้เขียนมาตรฐานรู้จักชัดเจนในเหตุผล ...
supercat

1
... ว่าเป็นไปได้สำหรับการดำเนินการที่จะสอดคล้องโดยไม่ได้มีคุณภาพดีพอที่จะเหมาะสำหรับวัตถุประสงค์ใด ๆ แต่พวกเขาไม่คิดว่าจำเป็นต้องป้องกันสิ่งนั้น)
supercat

1
@syntaxerror วิธีการยืมจาก C ++ เมื่อ C มีอายุมากกว่าทศวรรษกว่า C ++ (ทั้งในรุ่นแรกและมาตรฐานแรก)
phuclv

178

การใช้งานอีกอย่างvolatileคือตัวจัดการสัญญาณ หากคุณมีรหัสเช่นนี้:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

คอมไพเลอร์ได้รับอนุญาตให้สังเกตเห็นร่างกายของลูปไม่ได้สัมผัสquitตัวแปรและแปลงลูปเป็นwhile (true)ลูป แม้ว่าquitตัวแปรจะถูกตั้งค่าบนตัวจัดการสัญญาณสำหรับSIGINTและSIGTERM; คอมไพเลอร์ไม่มีทางรู้ว่า

อย่างไรก็ตามหากquitมีการประกาศตัวแปรvolatileคอมไพเลอร์จะถูกบังคับให้โหลดทุกครั้งเพราะสามารถแก้ไขได้ที่อื่น นี่คือสิ่งที่คุณต้องการในสถานการณ์นี้


เมื่อคุณพูดว่า "คอมไพเลอร์ถูกบังคับให้โหลดทุกครั้งมันเป็นเหมือนเมื่อคอมไพเลอร์ตัดสินใจที่จะเพิ่มประสิทธิภาพของตัวแปรบางอย่างและเราไม่ได้ประกาศตัวแปรเป็นสารระเหยในเวลาทำงานที่ตัวแปรบางตัวถูกโหลดลงทะเบียน CPU ที่ไม่ได้อยู่ในหน่วยความจำ ?
Amit Singh Tomar

1
@AmitSinghTomar มันหมายถึงสิ่งที่พูด: ทุกครั้งที่โค้ดตรวจสอบค่าจะถูกโหลดซ้ำ มิฉะนั้นคอมไพเลอร์ได้รับอนุญาตให้สันนิษฐานว่าฟังก์ชั่นที่ไม่ได้อ้างอิงถึงตัวแปรไม่สามารถแก้ไขได้ดังนั้นสมมติว่า CesarB ตั้งใจที่ไม่ได้ตั้งค่าลูปข้างต้นquitคอมไพเลอร์สามารถปรับให้เป็นวงคงที่โดยสมมติว่า ที่ไม่มีทางquitเปลี่ยนระหว่างการวนซ้ำ หมายเหตุ: นี่ไม่จำเป็นต้องเป็นการทดแทนที่ดีสำหรับการเขียนโปรแกรม threadsafe จริง
underscore_d

ถ้าออกเป็นตัวแปรทั่วโลกแล้วคอมไพเลอร์จะต้องไม่ปรับห่วงขณะที่ถูกต้องหรือไม่
Pierre G.

2
@PierreG ไม่คอมไพเลอร์สามารถสันนิษฐานได้ว่าโค้ดนั้นเป็นเธรดเดี่ยวเว้นแต่จะแจ้งเป็นอย่างอื่น นั่นคือในกรณีที่ไม่มีvolatileหรือเครื่องหมายอื่น ๆ มันจะคิดว่าไม่มีอะไรนอกลูปแก้ไขตัวแปรนั้นเมื่อมันเข้าสู่วงแม้ว่ามันจะเป็นตัวแปรทั่วโลก
CesarB

1
@PierreG ใช่ลองเช่นการรวบรวมextern int global; void fn(void) { while (global != 0) { } }ด้วยgcc -O3 -Sและดูที่ไฟล์การชุมนุมที่เกิดขึ้นในเครื่องของฉันมันไม่movl global(%rip), %eax; testl %eax, %eax; je .L1; .L4: jmp .L4นั่นคือการวนซ้ำไม่สิ้นสุดหากโกลบอลไม่ใช่ศูนย์ จากนั้นลองเพิ่มvolatileและดูความแตกต่าง
CesarB

60

volatileบอกคอมไพเลอร์ว่าตัวแปรของคุณอาจถูกเปลี่ยนโดยวิธีอื่นนอกเหนือจากรหัสที่เข้าถึงได้ เช่นอาจเป็นตำแหน่งหน่วยความจำ I / O ที่แมป หากไม่ได้ระบุไว้ในกรณีเช่นนี้การเข้าถึงตัวแปรบางอย่างสามารถปรับให้เหมาะสมเช่นเนื้อหาสามารถเก็บไว้ในรีจิสเตอร์และตำแหน่งหน่วยความจำไม่ได้อ่านอีกครั้ง


30

ดูบทความนี้โดย Andrei Alexandrescu, " volatile - เพื่อนที่ดีที่สุดของโปรแกรมเมอร์แบบมัลติเธรด "

ระเหยคำหลักได้วางแผนเพื่อป้องกันการเพิ่มประสิทธิภาพคอมไพเลอร์ที่อาจทำให้รหัสไม่ถูกต้องในการปรากฏตัวของเหตุการณ์บางอย่างไม่ตรงกัน ตัวอย่างเช่นถ้าคุณกำหนดตัวแปรดั้งเดิมเป็น สารระเหย คอมไพเลอร์ไม่ได้รับอนุญาตให้แคชในการลงทะเบียนซึ่งเป็นการเพิ่มประสิทธิภาพทั่วไปที่จะเกิดความเสียหายหากตัวแปรนั้นถูกใช้ร่วมกันในหลายเธรด ดังนั้นกฎทั่วไปคือถ้าคุณมีตัวแปรประเภทดั้งเดิมที่ต้องใช้ร่วมกันในหลายกระทู้ประกาศตัวแปรเหล่านั้นผันผวนแต่จริง ๆ แล้วคุณสามารถทำได้มากขึ้นด้วยคำหลักนี้: คุณสามารถใช้มันเพื่อจับรหัสที่ไม่ได้เป็นเธรดที่ปลอดภัยและคุณสามารถทำได้ในเวลารวบรวม บทความนี้แสดงวิธีการทำ วิธีการแก้ปัญหาเกี่ยวข้องกับตัวชี้สมาร์ทที่เรียบง่ายที่ยังทำให้มันง่ายต่อการอนุกรมซีเรียลส่วนที่สำคัญของรหัส

บทความนำไปใช้กับทั้งสองและCC++

โปรดดูบทความ " C ++ และภัยคุกคามจากการล็อคซ้ำซ้อน " โดย Scott Meyers และ Andrei Alexandrescu:

ดังนั้นเมื่อจัดการกับตำแหน่งหน่วยความจำบางส่วน (เช่นพอร์ตที่แมปหน่วยความจำหรือหน่วยความจำที่อ้างอิงโดย ISRs [Interrupt Service Routines]) การปรับแต่งบางอย่างจะต้องถูกระงับ มีความผันผวนสำหรับการระบุการดูแลเป็นพิเศษสำหรับสถานที่ดังกล่าวโดยเฉพาะ: (1) เนื้อหาของตัวแปรระเหยคือ "ไม่เสถียร" (สามารถเปลี่ยนแปลงได้โดยวิธีการที่ไม่รู้จักคอมไพเลอร์), (2) ทั้งหมดเขียนไปยังข้อมูลระเหย จะต้องดำเนินการทางศาสนาและ (3) การดำเนินการทั้งหมดเกี่ยวกับข้อมูลที่ระเหยได้ดำเนินการตามลำดับที่ปรากฏในซอร์สโค้ด กฎสองข้อแรกเพื่อให้แน่ใจว่าการอ่านและการเขียนที่เหมาะสม อันสุดท้ายให้ใช้งานโพรโทคอล I / O ที่ผสมอินพุตและเอาต์พุต นี่คือสิ่งที่ไม่แน่นอนว่า C และ C ++ รับประกันความผันผวนได้อย่างไร


มาตรฐานระบุว่าการอ่านนั้นถูกพิจารณาว่าเป็น 'พฤติกรรมที่สังเกตได้' หรือไม่หากไม่เคยใช้ค่า ความประทับใจของฉันคือมันควรจะเป็น แต่เมื่อฉันอ้างว่ามันเป็นที่อื่นมีคนท้าทายฉันสำหรับการอ้างอิง สำหรับฉันแล้วดูเหมือนว่าบนแพลตฟอร์มใด ๆ ที่การอ่านตัวแปรที่ระเหยได้อาจมีผลกระทบใด ๆ คอมไพเลอร์จะต้องสร้างโค้ดที่ดำเนินการทุกครั้งที่อ่านอย่างแม่นยำ หากไม่มีข้อกำหนดนั้นจะเป็นการยากที่จะเขียนโค้ดที่สร้างลำดับการอ่านที่คาดการณ์ได้
supercat

@supercat: ตามบทความแรก "ถ้าคุณใช้ตัวแก้ไขแบบระเหยบนตัวแปรคอมไพเลอร์จะไม่แคชตัวแปรนั้นในการลงทะเบียน - การเข้าถึงแต่ละครั้งจะไปถึงตำแหน่งหน่วยความจำจริงของตัวแปรนั้น" นอกจากนี้ในส่วน§6.7.3.6ของมาตรฐาน c99 มันบอกว่า: "วัตถุที่มีคุณสมบัติที่มีความสามารถในการระเหยอาจถูกปรับเปลี่ยนในรูปแบบที่ไม่รู้จักในการนำไปใช้หรือมีผลข้างเคียงอื่น ๆ ที่ไม่รู้จัก" นอกจากนี้ยังหมายถึงว่าตัวแปรที่ระเหยได้อาจไม่ถูกแคชในรีจิสเตอร์และการอ่านและการเขียนทั้งหมดจะต้องถูกดำเนินการตามลำดับที่สัมพันธ์กับลำดับของลำดับซึ่งเป็นที่สังเกตได้จริง
Robert S. Barnes

บทความหลังแน่นอนระบุอย่างชัดเจนว่าการอ่านเป็นผลข้างเคียง อดีตบ่งชี้ว่าการอ่านไม่สามารถดำเนินการได้ตามลำดับ แต่ดูเหมือนจะไม่ขัดขวางความเป็นไปได้ของการอ่านทั้งหมด
supercat

"คอมไพเลอร์ที่ไม่ได้รับอนุญาตให้แคชในการลงทะเบียน" - arcitectures RISC ส่วนใหญ่ ae ลงทะเบียนเครื่องใด ๆ ดังนั้นอ่านแก้ไขเขียนมีการแคชวัตถุในรีจิ volatileไม่รับประกันอะตอมมิก
ซื่อสัตย์เกินไปสำหรับไซต์นี้

1
@Olaf: การโหลดบางสิ่งบางอย่างลงทะเบียนไม่เหมือนกับการแคช การแคชจะมีผลกับจำนวนโหลดหรือร้านค้าหรือเวลาของพวกเขา
supercat

28

คำอธิบายง่ายๆของฉันคือ:

ในบางสถานการณ์ตามตรรกะหรือรหัสคอมไพเลอร์จะทำการปรับให้เหมาะสมของตัวแปรที่คิดว่าจะไม่เปลี่ยนแปลง volatileป้องกันคำหลักตัวแปรถูกปรับ

ตัวอย่างเช่น:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

จากโค้ดด้านบนคอมไพเลอร์อาจคิดว่าusb_interface_flagเป็น 0 และในขณะที่ลูปจะเป็นศูนย์ตลอดไป หลังจากการปรับให้เหมาะสมคอมไพเลอร์จะถือว่าเป็นwhile(true)ตลอดเวลาทำให้เกิดการวนซ้ำไม่สิ้นสุด

เพื่อหลีกเลี่ยงสถานการณ์ประเภทนี้เราขอประกาศว่าค่าสถานะเป็นความผันผวนเรากำลังบอกให้คอมไพเลอร์ทราบว่าค่านี้อาจเปลี่ยนแปลงได้โดยอินเทอร์เฟซภายนอกหรือโมดูลของโปรแกรมอื่น ๆ เช่นโปรดอย่าปรับให้เหมาะสม นั่นคือกรณีการใช้งานสำหรับการระเหย


19

การใช้งานเล็กน้อยสำหรับการระเหยมีดังต่อไปนี้ สมมติว่าคุณต้องการคำนวณอนุพันธ์เชิงตัวเลขของฟังก์ชันf:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

ปัญหาคือว่าx+h-xโดยทั่วไปไม่เท่ากับhเนื่องจากข้อผิดพลาดในการ roundoff คิดเกี่ยวกับมัน: เมื่อคุณลบล้างตัวเลขที่ใกล้ชิดมากคุณจะสูญเสียตัวเลขจำนวนมากที่สามารถทำลายการคำนวณอนุพันธ์ (คิด 1.00001 - 1) วิธีแก้ปัญหาที่เป็นไปได้อาจเป็น

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

แต่ขึ้นอยู่กับแพลตฟอร์มและสวิตช์คอมไพเลอร์ของคุณบรรทัดที่สองของฟังก์ชันนั้นอาจถูกลบออกโดยคอมไพเลอร์ที่ปรับให้เหมาะสมที่สุด ดังนั้นคุณเขียนแทน

    volatile double hh = x + h;
    hh -= x;

เพื่อบังคับให้คอมไพเลอร์อ่านตำแหน่งหน่วยความจำที่มี hh โดยไม่เสียโอกาสในการปรับให้เหมาะสมที่สุด


อะไรคือความแตกต่างระหว่างการใช้hหรือhhในสูตรอนุพันธ์? เมื่อทำการhhคำนวณแล้วสูตรสุดท้ายจะใช้เช่นเดียวกับสูตรแรกโดยไม่มีความแตกต่าง บางทีมันควรจะเป็น(f(x+h) - f(x))/hh?
Sergey Zhukov

2
ความแตกต่างระหว่างhและhhเป็นที่ถูกตัดทอนบางส่วนอำนาจเชิงลบของทั้งสองโดยการดำเนินการhh x + h - xในกรณีนี้x + hhและแตกต่างกันตรงโดยx hhนอกจากนี้คุณยังสามารถใช้สูตรของคุณมันจะให้ผลลัพธ์ที่เหมือนกันเนื่องจากx + hและx + hhเท่ากัน (เป็นตัวส่วนที่สำคัญที่นี่)
Alexandre C.

3
ไม่ใช่วิธีที่ง่ายกว่าในการเขียนสิ่งนี้x1=x+h; d = (f(x1)-f(x))/(x1-x)ใช่ไหม โดยไม่ต้องใช้สารระเหย
Sergey Zhukov

การอ้างอิงใด ๆ ที่คอมไพเลอร์สามารถล้างบรรทัดที่สองของฟังก์ชันได้หรือไม่
CoffeeTableEspresso

@CoffeeTableEspresso: ไม่ขอโทษ ยิ่งฉันรู้เกี่ยวกับจุดลอยตัวมากเท่าไหร่ฉันก็ยิ่งเชื่อว่าคอมไพเลอร์ได้รับอนุญาตให้ปรับให้เหมาะสมเท่านั้นหากบอกอย่างชัดเจนด้วย-ffast-mathหรือเทียบเท่า
Alexandre C.

11

มีอยู่สองวิธี สิ่งเหล่านี้ถูกใช้เป็นพิเศษในการพัฒนาแบบฝังตัว

  1. คอมไพเลอร์จะไม่ปรับฟังก์ชั่นที่ใช้ตัวแปรที่กำหนดด้วยคำสำคัญระเหย

  2. Volatile ใช้ในการเข้าถึงตำแหน่งหน่วยความจำที่แน่นอนใน RAM, ROM, ฯลฯ ... นี่คือการใช้บ่อยขึ้นเพื่อควบคุมอุปกรณ์ที่แมปหน่วยความจำเข้าถึง CPU ลงทะเบียนและค้นหาตำแหน่งหน่วยความจำที่เฉพาะเจาะจง

ดูตัวอย่างที่มีรายชื่อประกอบ เรื่องการใช้คำสำคัญ "ระเหย" C ในการพัฒนาแบบฝัง


"คอมไพเลอร์จะไม่ปรับฟังก์ชั่นที่ใช้ตัวแปรที่กำหนดด้วยคำสำคัญระเหย" - ที่ผิดธรรมดา
ซื่อสัตย์เกินไปสำหรับไซต์นี้

10

สารระเหยยังมีประโยชน์เมื่อคุณต้องการบังคับให้คอมไพเลอร์ไม่ปรับลำดับรหัสเฉพาะ (เช่นสำหรับการเขียนเกณฑ์มาตรฐานขนาดเล็ก)


10

ฉันจะพูดถึงสถานการณ์อื่นที่ความสำคัญของสารระเหย

สมมติว่าคุณแมปหน่วยความจำ - ไฟล์สำหรับ I / O ที่เร็วขึ้นและไฟล์นั้นสามารถเปลี่ยนแปลงได้เบื้องหลัง (เช่นไฟล์ไม่ได้อยู่ในฮาร์ดไดรฟ์ในเครื่องของคุณ แต่ให้บริการผ่านเครือข่ายโดยคอมพิวเตอร์เครื่องอื่น)

หากคุณเข้าถึงข้อมูลของไฟล์หน่วยความจำที่แมปผ่านตัวชี้ไปยังวัตถุที่ไม่ลบเลือน (ที่ระดับซอร์สโค้ด) รหัสที่สร้างโดยคอมไพเลอร์สามารถดึงข้อมูลเดียวกันหลาย ๆ ครั้งโดยที่คุณไม่รู้ตัว

หากข้อมูลนั้นมีการเปลี่ยนแปลงโปรแกรมของคุณอาจใช้ข้อมูลสองเวอร์ชันขึ้นไปและเข้าสู่สถานะที่ไม่สอดคล้องกัน สิ่งนี้สามารถนำไปสู่พฤติกรรมที่ไม่ถูกต้องตามหลักเหตุผลของโปรแกรม แต่ยังรวมถึงช่องโหว่ด้านความปลอดภัยที่สามารถนำไปใช้ประโยชน์ได้หากโปรแกรมนั้นประมวลผลไฟล์หรือไฟล์ที่ไม่น่าเชื่อถือจากตำแหน่งที่ไม่น่าเชื่อถือ

หากคุณสนใจเรื่องความปลอดภัยและควรทำนี่เป็นสถานการณ์สำคัญที่ต้องพิจารณา


7

volatile หมายถึงที่เก็บข้อมูลมีแนวโน้มที่จะเปลี่ยนแปลงได้ตลอดเวลาและเปลี่ยนแปลงได้ แต่มีบางสิ่งที่อยู่นอกเหนือการควบคุมของโปรแกรมผู้ใช้ ซึ่งหมายความว่าหากคุณอ้างอิงตัวแปรโปรแกรมควรตรวจสอบที่อยู่ทางกายภาพเสมอ (เช่นการป้อนข้อมูลที่แมปแมป) และไม่ใช้ในทางที่แคช


ไม่มีคอมไพเลอร์ใช้ความผันผวนเพื่อหมายถึง "ที่อยู่ทางกายภาพใน RAM" หรือ "บายพาสแคช"
curiousguy


5

volatileในความคิดของคุณไม่ควรคาดหวังมากเกินไปจาก เพื่อแสดงให้เห็นดูตัวอย่างในNils Pipenbrinck เป็นอย่างมากโหวตคำตอบ

volatileผมจะบอกว่าตัวอย่างของเขาไม่เหมาะสำหรับ volatileใช้เพื่อ: ป้องกันคอมไพเลอร์ไม่ให้ทำการปรับให้เป็นประโยชน์และเป็นที่ต้องการเท่านั้น ไม่มีอะไรเกี่ยวกับเธรดที่ปลอดภัยการเข้าถึงของอะตอมหรือแม้แต่ลำดับของหน่วยความจำ

ในตัวอย่างนั้น:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

gadget->data = dataก่อนgadget->command = commandเพียง แต่มีการประกันเฉพาะในรหัสเรียบเรียงโดยรวบรวม ณ รันไทม์ตัวประมวลผลยังอาจเรียงลำดับข้อมูลและการกำหนดคำสั่งอีกครั้งเกี่ยวกับสถาปัตยกรรมตัวประมวลผล ฮาร์ดแวร์อาจได้รับข้อมูลที่ไม่ถูกต้อง (สมมติว่ามีการจับคู่แกดเจ็ตกับฮาร์ดแวร์ I / O) จำเป็นต้องมีกำแพงกั้นหน่วยความจำระหว่างข้อมูลและการกำหนดคำสั่ง


2
ฉันว่า volatile ใช้เพื่อป้องกันคอมไพเลอร์จากการปรับให้เหมาะสมที่โดยปกติจะมีประโยชน์และเป็นที่ต้องการ ตามที่เขียนไว้ดูเหมือนvolatileจะลดประสิทธิภาพลงโดยไม่มีเหตุผล ไม่ว่าจะเพียงพอหรือไม่นั้นจะขึ้นอยู่กับแง่มุมอื่น ๆ ของระบบที่โปรแกรมเมอร์อาจรู้มากกว่าคอมไพเลอร์ ในทางตรงกันข้ามหากหน่วยประมวลผลรับประกันว่าคำสั่งในการเขียนไปยังที่อยู่บางแห่งจะล้างแคช CPU แต่คอมไพเลอร์ไม่สามารถล้างตัวแปร register-cached ที่ CPU ไม่รู้เรื่องการล้างแคชจะไร้ประโยชน์
supercat

5

ในภาษาที่ออกแบบโดยเดนนิสริตชี่ทุก ๆ การเข้าถึงวัตถุใด ๆ นอกเหนือจากวัตถุอัตโนมัติที่ไม่ได้รับที่อยู่จะทำตัวราวกับว่ามันคำนวณที่อยู่ของวัตถุแล้วอ่านหรือเขียนที่เก็บข้อมูลที่อยู่นั้น สิ่งนี้ทำให้ภาษามีประสิทธิภาพมาก แต่มีโอกาสในการเพิ่มประสิทธิภาพที่ จำกัด อย่างรุนแรง

ในขณะที่มันอาจเป็นไปได้ที่จะเพิ่ม qualifier ที่จะเชิญคอมไพเลอร์ให้สมมติว่าวัตถุเฉพาะจะไม่ถูกเปลี่ยนในรูปแบบแปลก ๆ ข้อสันนิษฐานดังกล่าวเหมาะสมสำหรับวัตถุส่วนใหญ่ในโปรแกรม C และมันจะมี ถูกทำไม่ได้เพื่อเพิ่มคุณสมบัติให้กับวัตถุทั้งหมดที่สมมติฐานดังกล่าวจะเหมาะสม ในทางกลับกันบางโปรแกรมจำเป็นต้องใช้วัตถุบางอย่างซึ่งการสันนิษฐานดังกล่าวจะไม่ถือ เมื่อต้องการแก้ไขปัญหานี้มาตรฐานบอกว่าคอมไพเลอร์อาจสันนิษฐานว่าวัตถุที่ไม่ได้ประกาศvolatileจะไม่มีค่าสังเกตหรือเปลี่ยนแปลงในลักษณะที่อยู่นอกการควบคุมของคอมไพเลอร์หรือจะอยู่นอกความเข้าใจที่สมเหตุสมผลของคอมไพเลอร์

เพราะแพลตฟอร์มต่างๆอาจมีวิธีการที่แตกต่างกันในการที่วัตถุที่อาจจะมีการตั้งข้อสังเกตหรือการปรับเปลี่ยนอยู่นอกเหนือการควบคุมของคอมไพเลอร์ก็เป็นสิ่งที่เหมาะสมที่คอมไพเลอร์ที่มีคุณภาพสำหรับแพลตฟอร์มเหล่านั้นควรจะแตกต่างกันในการจัดการแน่นอนของพวกเขาของvolatileความหมาย น่าเสียดายเนื่องจาก Standard ล้มเหลวในการแนะนำว่าคอมไพเลอร์คุณภาพสำหรับการเขียนโปรแกรมระดับต่ำบนแพลตฟอร์มควรจัดการvolatileในลักษณะที่จะรับรู้ถึงผลกระทบที่เกี่ยวข้องใด ๆ และทั้งหมดของการดำเนินการอ่าน / เขียนเฉพาะบนแพลตฟอร์มนั้นคอมไพเลอร์หลายตัว ดังนั้นในวิธีที่ทำให้ยากขึ้นในการประมวลผลสิ่งต่าง ๆ เช่นพื้นหลัง I / O ในวิธีที่มีประสิทธิภาพ แต่ไม่สามารถถูกทำลายโดย "การเพิ่มประสิทธิภาพ" ของคอมไพเลอร์


5

กล่าวง่ายๆว่าคอมไพเลอร์ไม่ทำการปรับให้เหมาะสมกับตัวแปรเฉพาะ ตัวแปรที่แมปกับการลงทะเบียนอุปกรณ์จะถูกปรับเปลี่ยนโดยอ้อมโดยอุปกรณ์ ในกรณีนี้ต้องใช้สารระเหย


1
มีอะไรใหม่ในคำตอบนี้ที่ไม่ได้พูดถึงมาก่อนหรือไม่?
slfan

3

สารระเหยสามารถเปลี่ยนแปลงได้จากภายนอกโค้ดที่คอมไพล์ (ตัวอย่างเช่นโปรแกรมอาจแม็พตัวแปรระเหยกับหน่วยความจำที่แม็พลงทะเบียน) คอมไพเลอร์จะไม่ใช้การปรับให้เหมาะสมกับโค้ดที่จัดการกับตัวแปรระเหย - ตัวอย่างเช่น ไม่โหลดลงในทะเบียนโดยไม่ต้องเขียนลงในหน่วยความจำ นี่เป็นสิ่งสำคัญเมื่อจัดการกับการลงทะเบียนฮาร์ดแวร์


0

ตามคำแนะนำที่ถูกต้องจากที่นี่การใช้คำหลักที่มีความผันผวนได้รับความนิยมคือการข้ามการเพิ่มประสิทธิภาพของตัวแปรที่ผันผวน

ข้อได้เปรียบที่ดีที่สุดที่อยู่ในใจและมูลค่าการพูดถึงหลังจากอ่านเกี่ยวกับความผันผวนคือ - เพื่อป้องกันไม่ให้ย้อนกลับของตัวแปรในกรณีของlongjmpของตัวแปรในกรณีของการกระโดดที่ไม่ใช่ท้องถิ่น

สิ่งนี้หมายความว่า?

หมายความว่าค่าสุดท้ายจะคงอยู่หลังจากที่คุณคลายสแต็กเพื่อกลับไปยังเฟรมสแต็กก่อนหน้า โดยทั่วไปในกรณีที่มีสถานการณ์ที่ผิดพลาด

เนื่องจากมันอยู่นอกขอบเขตของคำถามนี้ฉันจะไม่ลงรายละเอียดsetjmp/longjmpที่นี่ แต่คุ้มค่าที่จะอ่าน และคุณลักษณะความผันผวนสามารถใช้เพื่อรักษาค่าสุดท้ายได้อย่างไร


-2

ไม่อนุญาตให้คอมไพเลอร์เปลี่ยนค่าตัวแปรโดยอัตโนมัติ ตัวแปรระเหยสำหรับการใช้งานแบบไดนามิก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.