ตัวแปรโลคัลแบบไม่กำหนดค่าเริ่มต้นเป็นตัวสร้างตัวเลขสุ่มที่เร็วที่สุดหรือไม่


329

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

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

มันเร็วกว่าไหม

void updateEffect(){
    for(int i=0;i<1000;i++){
        star[i].setColor(rand()%255,rand()%255,rand()%255);
        star[i].setVisible(rand()%2==0?true:false);
    }
}

และยังเร็วกว่าตัวสร้างตัวเลขสุ่มอื่น ๆ


88
+1 นี่เป็นคำถามที่ถูกต้องสมบูรณ์ เป็นความจริงที่ว่าในทางปฏิบัติค่าเริ่มต้นอาจเป็นแบบสุ่ม ความจริงที่ว่าพวกเขาไม่ได้โดยเฉพาะอย่างยิ่งและมันเป็น UB ไม่ได้ถามว่าเลว
imallett

35
@imallett: อย่างแน่นอน นี่เป็นคำถามที่ดีและเกม Z80 (Amstrad / ZX Spectrum) อย่างน้อยหนึ่งเกมในอดีตใช้โปรแกรมเป็นข้อมูลในการตั้งค่าภูมิประเทศ ดังนั้นจึงมีแม้กระทั่งทำนอง ไม่สามารถทำได้ในวันนี้ ระบบปฏิบัติการที่ทันสมัยนำความสนุกออกไป
Bathsheba

81
แน่นอนปัญหาหลักคือมันไม่สุ่ม
john

30
ในความเป็นจริงมีตัวอย่างของตัวแปร uninitialized ที่ใช้เป็นค่าสุ่มดูDebian RNG disaster (ตัวอย่างที่ 4 ในบทความนี้ )
PaperBirdMaster

31
ในทางปฏิบัติ - และเชื่อฉันฉันทำดีบั๊กมากมายเกี่ยวกับสถาปัตย์กรรมต่าง ๆ - วิธีการแก้ปัญหาของคุณสามารถทำได้สองอย่าง: อ่านการลงทะเบียนแบบไม่กำหนดค่าเริ่มต้นหรือหน่วยความจำไม่เริ่มต้น ขณะนี้ในขณะที่ "uninitialized" หมายถึงการสุ่มในบางลักษณะในทางปฏิบัติมันน่าจะประกอบด้วยa) zeroes , b) การ ทำซ้ำหรือค่าที่สอดคล้องกัน (ในกรณีที่หน่วยความจำการอ่านเดิมครอบครองสื่อดิจิทัล) หรือc) ขยะที่สอดคล้องกับค่า จำกัด ชุด (ในกรณีที่หน่วยความจำการอ่านก่อนหน้านี้ครอบครองโดยข้อมูลดิจิตอลที่เข้ารหัส) ไม่มีแหล่งใดเป็นแหล่งข้อมูลเอนโทรปีของจริง
mg30rg

คำตอบ:


299

ดังที่คนอื่น ๆ ระบุไว้นี่เป็นพฤติกรรมที่ไม่ได้กำหนด (UB)

ในทางปฏิบัติมันจะทำงานได้จริง การอ่านจากการลงทะเบียนโดยไม่มีการกำหนดค่าบน x86 [-64] สถาปัตยกรรมจะสร้างผลลัพธ์ขยะและอาจจะไม่ทำอะไรเลวร้าย (เมื่อเทียบกับเช่น Itanium ซึ่งการลงทะเบียนสามารถถูกตั้งค่าสถานะเป็นไม่ถูกต้องเพื่ออ่านข้อผิดพลาดแพร่กระจายเช่น NaN)

มีสองปัญหาหลักว่า:

  1. มันจะไม่สุ่มโดยเฉพาะ ในกรณีนี้คุณกำลังอ่านจากสแต็กดังนั้นคุณจะได้สิ่งที่เคยมีมาก่อนหน้านี้ ซึ่งอาจมีการสุ่มโครงสร้างที่สมบูรณ์รหัสผ่านที่คุณป้อนเมื่อสิบนาทีก่อนหรือสูตรคุกกี้ของคุณยาย

  2. เป็นการปฏิบัติที่ไม่ดี (ตัวใหญ่ 'B')เพื่อปล่อยให้สิ่งเหล่านี้คืบคลานเข้าไปในโค้ดของคุณ ในทางเทคนิคคอมไพเลอร์สามารถแทรกreformat_hdd();ทุกครั้งที่คุณอ่านตัวแปรที่ไม่ได้กำหนด มันจะไม่เกิดขึ้น แต่คุณไม่ควรทำเช่นนั้น อย่าทำสิ่งที่ไม่ปลอดภัย ยิ่งคุณมีข้อยกเว้นน้อยลงเท่าไหร่คุณก็ยิ่งปลอดภัยจากความผิดพลาดที่เกิดขึ้นตลอดเวลา

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

นอกจากนี้ฉันจะยิงคุณ


39
@Potatoswatter: การลงทะเบียน Itanium สามารถมี NaT (ไม่ใช่สิ่งที่) ซึ่งผลคือ "ลงทะเบียนเริ่มต้น" บน Itanium การอ่านจากการลงทะเบียนเมื่อคุณยังไม่ได้เขียนอาจยกเลิกโปรแกรมของคุณ (อ่านเพิ่มเติมได้ที่นี่: blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx ) ดังนั้นจึงมีเหตุผลที่ดีว่าทำไมการอ่านค่าที่ไม่ได้กำหนดค่าเริ่มต้นนั้นเป็นพฤติกรรมที่ไม่ได้กำหนด อาจเป็นเหตุผลหนึ่งว่าทำไม Itanium ไม่ได้รับความนิยมมาก :)
30915 tbleher

58
ฉันคัดค้านแนวคิดที่ว่า "เป็นงาน" แม้ว่ามันจะเป็นจริงในวันนี้ซึ่งไม่เป็นเช่นนั้นก็อาจเปลี่ยนแปลงได้ตลอดเวลาเนื่องจากคอมไพเลอร์ที่ก้าวร้าวมากขึ้น คอมไพเลอร์สามารถแทนที่การอ่านด้วยunreachable()และลบครึ่งหนึ่งของโปรแกรมของคุณ สิ่งนี้จะเกิดขึ้นในทางปฏิบัติเช่นกัน พฤติกรรมนี้ทำให้เป็นกลาง RNG ใน Linux distro บางตัวที่ฉันเชื่ออย่างสมบูรณ์; คำตอบส่วนใหญ่ในคำถามนี้ดูเหมือนจะสันนิษฐานว่าค่าที่ไม่ได้กำหนดค่าเริ่มต้นจะทำงานเหมือนกับค่าเลย นั่นเป็นเท็จ
usr

25
นอกจากนี้ฉันจะยิงคุณดูเหมือนจะค่อนข้างโง่พูดสมมติว่าการปฏิบัติที่ดีนี้ควรถูกจับได้ในการทบทวนโค้ดอภิปรายและไม่ควรเกิดขึ้นอีก สิ่งนี้ควรถูกจับได้อย่างแน่นอนเนื่องจากเราใช้สัญญาณเตือนที่ถูกต้องใช่ไหม
Shafik Yaghmour

17
@Michael จริงๆแล้วมันคือ หากโปรแกรมมีพฤติกรรมที่ไม่ได้กำหนด ณ จุดใด ๆ คอมไพเลอร์สามารถปรับโปรแกรมของคุณในวิธีที่มีผลต่อรหัสก่อนหน้านั้นที่เรียกพฤติกรรมที่ไม่ได้กำหนด มีบทความต่าง ๆ และการสาธิตว่าสมองสามารถทำให้เรื่องนี้ดีขึ้นได้อย่างไร: blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx (ซึ่งรวมถึงบิตในมาตรฐานที่ระบุว่า การเดิมพันทั้งหมดจะปิดหากเส้นทางใด ๆ ในโปรแกรมของคุณเรียกใช้ UB)
Tom Tanner

19
คำตอบนี้ทำให้เสียงราวกับว่า"การกล่าวอ้างไม่ได้กำหนดพฤติกรรมที่ไม่ดีในทางทฤษฎี แต่มันจะไม่เจ็บจริงๆคุณมากในทางปฏิบัติ" มันผิด การจัดเก็บภาษีเอนโทรปีจากการแสดงออกที่จะทำให้เกิด UB สามารถ (และอาจจะ ) สาเหตุทั้งหมดเอนโทรปีเก็บมาก่อนหน้าจะหายไป นี่เป็นอันตรายร้ายแรง
Theodoros Chatzigiannakis

213

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

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

เราสามารถอ่านการเพิ่มประสิทธิภาพที่เป็นอันตรายของ CERT และการสูญเสียความเป็นเวร ( วิดีโอ ) ซึ่งกล่าวว่าเหนือสิ่งอื่นใด:

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

บ่อยครั้งที่การปรับให้เหมาะสมเหล่านี้รบกวนความสามารถของนักพัฒนาในการทำการวิเคราะห์สาเหตุ - ผลบนซอร์สโค้ดของพวกเขานั่นคือการวิเคราะห์การพึ่งพาของผลลัพธ์ดาวน์สตรีมในผลลัพธ์ก่อนหน้า

ดังนั้นการเพิ่มประสิทธิภาพเหล่านี้จึงกำจัดสาเหตุในซอฟต์แวร์และเพิ่มความน่าจะเป็นของความผิดพลาดของซอฟต์แวร์ข้อบกพร่องและช่องโหว่

โดยเฉพาะอย่างยิ่งเกี่ยวกับค่าที่ไม่แน่นอนรายงานข้อบกพร่องมาตรฐาน C 451: ความไม่แน่นอนของตัวแปรอัตโนมัติที่ไม่ได้กำหนดค่าเริ่มต้นสำหรับการอ่านที่น่าสนใจ ยังไม่ได้รับการแก้ไข แต่นำเสนอแนวคิดของค่าสั่นคลอนซึ่งหมายถึงความไม่แน่นอนของค่าอาจแพร่กระจายผ่านโปรแกรมและสามารถมีค่าไม่แน่นอนที่แตกต่างกันที่จุดที่แตกต่างกันในโปรแกรม

ฉันไม่รู้ตัวอย่างที่เกิดขึ้น แต่ ณ จุดนี้เราไม่สามารถแยกแยะได้

ตัวอย่างจริงไม่ใช่ผลลัพธ์ที่คุณคาดหวัง

คุณไม่น่าจะได้รับค่าสุ่ม คอมไพเลอร์สามารถเพิ่มประสิทธิภาพวงออกไปโดยสิ้นเชิง ตัวอย่างเช่นกรณีที่ง่ายนี้:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r ;
    }
}

เสียงดังกราวปรับมันให้เหมาะสม ( ดูภาพสด ):

updateEffect(int*):                     # @updateEffect(int*)
    retq

หรืออาจรับค่าศูนย์ทั้งหมดเช่นเดียวกับกรณีที่แก้ไขนี้:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r%255 ;
    }
}

ดูมันมีชีวิตอยู่ :

updateEffect(int*):                     # @updateEffect(int*)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 64(%rdi)
    movups  %xmm0, 48(%rdi)
    movups  %xmm0, 32(%rdi)
    movups  %xmm0, 16(%rdi)
    movups  %xmm0, (%rdi)
    retq

ทั้งสองกรณีนี้เป็นรูปแบบที่ยอมรับได้อย่างสมบูรณ์แบบของพฤติกรรมที่ไม่ได้กำหนด

หมายเหตุถ้าเราอยู่บน Itanium เราสามารถจบด้วยค่ากับดัก :

[... ] หากการลงทะเบียนเกิดขึ้นเพื่อเก็บค่าที่ไม่ได้เป็นสิ่งพิเศษให้อ่านกับดักการลงทะเบียนยกเว้นสำหรับคำแนะนำบางอย่าง [... ]

หมายเหตุสำคัญอื่น ๆ

มันเป็นเรื่องที่น่าสนใจที่จะสังเกตเห็นความแปรปรวนระหว่าง gcc กับเสียงดังในโครงการ UB Canariesว่าพวกเขาเต็มใจที่จะใช้ประโยชน์จากพฤติกรรมที่ไม่ได้กำหนดซึ่งเกี่ยวข้องกับหน่วยความจำที่ไม่ได้เตรียมการ บันทึกบทความ ( เหมืองเน้น ):

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

ดังที่ Matthieu M. ชี้ให้เห็นสิ่งที่โปรแกรมเมอร์ C ทุกคนควรรู้เกี่ยวกับพฤติกรรมที่ไม่ได้กำหนด # 2/3นั้นเกี่ยวข้องกับคำถามนี้ มันบอกว่าท่ามกลางสิ่งอื่น ๆ ( เน้นฉัน ):

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

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

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


1
+ (int) (PI / 3) สำหรับตัวอย่างเอาต์พุตคอมไพเลอร์; เป็นตัวอย่างในชีวิตจริงที่ UB คือดีUB

2
การใช้ UB อย่างมีประสิทธิภาพนั้นเป็นเครื่องหมายการค้าของแฮ็กเกอร์ที่ยอดเยี่ยม ประเพณีนี้ดำเนินต่อไปนานกว่า 50 ปีหรือมากกว่านั้น น่าเสียดายที่ตอนนี้คอมพิวเตอร์จำเป็นต้องลดผลกระทบของ UB เนื่องจากคนไม่ดี ฉันสนุกกับการหาวิธีการทำสิ่งที่ยอดเยี่ยมด้วยรหัสเครื่อง UB หรือพอร์ตอ่าน / เขียน ฯลฯ ฉัน 90s เมื่อระบบปฏิบัติการไม่สามารถปกป้องผู้ใช้จากตัวเอง
sfdcfox

1
@sfdcfox ถ้าคุณทำมันในรหัสเครื่อง / แอสเซมเบลอร์มันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนด (อาจเป็นพฤติกรรมที่ไม่ธรรมดา)
Caleth

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

2
@ 500-InternalServerError เพราะอาจไม่สามารถตรวจพบได้ง่ายหรืออาจไม่สามารถตรวจพบได้ในกรณีทั่วไปดังนั้นจึงไม่มีวิธีที่จะห้ามพวกเขา ซึ่งแตกต่างจากนั้นการละเมิดไวยากรณ์ซึ่งสามารถตรวจพบได้ นอกจากนี้เรายังมีรูปแบบที่ไม่ดีและไม่มีรูปแบบที่ไม่จำเป็นต้องวินิจฉัยซึ่งโดยทั่วไปแล้วโปรแกรมที่มีรูปแบบไม่ดีซึ่งสามารถตรวจพบได้ในทางทฤษฎีจากสิ่งที่อยู่ในทฤษฎีนั้นไม่สามารถตรวจพบได้อย่างน่าเชื่อถือ
Shafik Yaghmour

164

ไม่มันแย่มาก

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

หากคุณต้องการตัวสร้างตัวเลขสุ่ม "ที่รวดเร็วและสกปรก" rand()นี่คือทางออกที่ดีที่สุดของคุณ ในการดำเนินการสิ่งที่มันทำคือการคูณการบวกและโมดูลัส

ตัวสร้างที่เร็วที่สุดที่ฉันรู้จักต้องการให้คุณใช้ a uint32_tเป็นประเภทของตัวแปรสุ่มหลอกIและใช้

I = 1664525 * I + 1013904223

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


9
เครื่องมือสร้าง "เชิงเส้นเชิงเส้น" ที่คุณนำเสนอนั้นเหมาะสำหรับแอปพลิเคชันทั่วไป แต่สำหรับแอปพลิเคชันที่ไม่ใช่การเข้ารหัสเท่านั้น มันเป็นไปได้ที่จะทำนายพฤติกรรมของมัน ดูตัวอย่าง "การถอดรหัสการเข้ารหัสเชิงเส้นที่เป็นเชิงเส้น " โดย Don Knuth เอง (ธุรกรรมธุรกรรม IEEE เกี่ยวกับทฤษฎีข้อมูล, เล่มที่ 31)
Jay

24
@Jay เมื่อเทียบกับตัวแปรที่กำหนดค่าได้อย่างรวดเร็วและสกปรก? นี่เป็นทางออกที่ดีกว่ามาก
Mike McMahon

2
rand()ในความคิดของฉันไม่เหมาะสำหรับวัตถุประสงค์และควรถูกคัดค้านทั้งหมด วันนี้คุณสามารถดาวน์โหลดเครื่องกำเนิดเลขสุ่มที่ได้รับใบอนุญาตและยอดเยี่ยมมากมาย (เช่น Mersenne Twister) ซึ่งเกือบจะเร็วที่สุดด้วยความง่ายที่สุดดังนั้นจึงไม่จำเป็นต้องใช้ข้อบกพร่องอย่างต่อเนื่องอีกต่อไปrand()
Jack Aidley

1
rand () มีปัญหาที่น่ากลัวอีกอย่างหนึ่ง: มันใช้การล็อคแบบหนึ่งซึ่งเรียกว่า Inside threads มันจะทำให้โค้ดของคุณช้าลงอย่างมาก อย่างน้อยก็มีรุ่น reentrant และถ้าคุณใช้ C ++ 11 API แบบสุ่มจะมอบทุกสิ่งที่คุณต้องการ
Marwan Burelle

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

42

คำถามที่ดี!

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

  1. เหมือนเดิมเสมอ
  2. เป็นหนึ่งในชุดเล็ก ๆ ของค่า
  3. รับค่าในช่วงขนาดเล็กตั้งแต่หนึ่งช่วงขึ้นไป
  4. ดูค่าจำนวนมากหารด้วย 2/4/8 จากตัวชี้บนระบบ 16/32/64 บิต
  5. ...

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

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

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


7
... และนั่นเป็นสิ่งที่ทำให้คอมไพเลอร์ - ออพติไมซ์มีประสิทธิภาพซึ่งน่าจะทำให้รหัสนั้นสมบูรณ์
Deduplicator

6 ... คุณจะได้รับ "การสุ่ม" ที่แตกต่างกันใน Debug and Release ไม่ได้กำหนดหมายความว่าคุณทำผิด
Sql Surfer

ขวา. ฉันจะย่อหรือสรุปด้วย "undefined"! = "arbitrary"! = "random" "ความไม่รู้จัก" ประเภทนี้มีคุณสมบัติแตกต่างกัน
fche

ตัวแปรทั่วโลกรับประกันว่าจะมีค่าที่กำหนดไม่ว่าจะเริ่มต้นอย่างชัดเจนหรือไม่ นี้แน่นอนจริงใน C ++และใน C เช่นกัน
Brian Vandenberg

32

คำตอบที่ดีมากมาย แต่ให้ฉันเพิ่มคำอื่นและเน้นจุดที่ในคอมพิวเตอร์ที่กำหนดขึ้นเองไม่มีอะไรสุ่ม สิ่งนี้เป็นจริงสำหรับทั้งตัวเลขที่ผลิตโดยหลอก -RNG และตัวเลข "สุ่ม" ที่ดูเหมือนจะพบในพื้นที่ของหน่วยความจำที่สงวนไว้สำหรับตัวแปรโลคัล C / C ++ บนสแต็ก

แต่ ... มันมีความแตกต่างที่สำคัญ

ตัวเลขที่สร้างโดยเครื่องกำเนิดไฟฟ้าเทียมเทียมที่ดีนั้นมีคุณสมบัติที่ทำให้พวกมันคล้ายกับการสุ่มจับอย่างแท้จริง ตัวอย่างเช่นการกระจายเป็นชุด ความยาวของรอบยาว: คุณสามารถรับตัวเลขสุ่มนับล้าน ๆ ก่อนที่รอบจะวนซ้ำ ลำดับไม่เกี่ยวข้องโดยอัตโนมัติ: ตัวอย่างเช่นคุณจะไม่เริ่มเห็นรูปแบบแปลก ๆ เกิดขึ้นหากคุณใส่หมายเลขที่ 2, 3 หรือ 27 หรือหากคุณดูตัวเลขเฉพาะในตัวเลขที่สร้างขึ้น

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

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

เมื่อฉันรวบรวมรหัสนี้กับ GCC บนเครื่อง Linux และรันมันจะกลายเป็นกำหนดค่อนข้างไม่เป็นที่พอใจ:

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

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

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

ที่จริงแล้วในฐานะคนอื่น ๆ ฉันจะพิจารณายิงใครบางคนที่พยายามจะถ่ายทอดความคิดนี้ในฐานะ "ประสิทธิภาพสูง RNG" อย่างจริงจัง


1
“ ในคอมพิวเตอร์ที่กำหนดขึ้นเองไม่มีอะไรสุ่ม” - นี่ไม่จริงเลย คอมพิวเตอร์สมัยใหม่ประกอบด้วยเซ็นเซอร์ทุกชนิดที่ช่วยให้คุณสร้างการสุ่มที่เป็นจริงและคาดเดาไม่ได้โดยไม่ต้องมีตัวกำเนิดฮาร์ดแวร์แยก ในสถาปัตยกรรมสมัยใหม่ค่านิยม/dev/randomมักมาจากแหล่งฮาร์ดแวร์ดังกล่าวและในความเป็นจริง "เสียงควอนตัม" กล่าวคือไม่สามารถคาดเดาได้อย่างแท้จริงในความรู้สึกทางกายภาพที่ดีที่สุดของคำ
Konrad Rudolph

2
แต่นั่นไม่ใช่คอมพิวเตอร์ที่กำหนดขึ้นแล้วใช่ไหม ตอนนี้คุณพึ่งพาข้อมูลด้านสิ่งแวดล้อม ไม่ว่าในกรณีใดสิ่งนี้จะทำให้เราเหนือกว่าการอภิปรายของหลอกหลอก -RNG ธรรมดากับบิต "สุ่ม" ในหน่วยความจำ นอกจากนี้ ... ดูที่คำอธิบายของ / dev / random เพื่อขอบคุณที่ไกลออกไปจากทางของพวกเขา implementers ไปเพื่อให้แน่ใจว่าตัวเลขสุ่มมีความปลอดภัย cryptographically ... แม่นยำเพราะแหล่งอินพุตไม่บริสุทธิ์, ควอนตัมเสียง uncorrelated ค่อนข้างอ่านค่าเซ็นเซอร์ที่มีความสัมพันธ์สูงซึ่งมีการสุ่มระดับเล็กน้อย มันค่อนข้างช้าเช่นกัน
Viktor Toth

29

พฤติกรรมที่ไม่ได้กำหนดหมายความว่าผู้เขียนคอมไพเลอร์มีอิสระที่จะเพิกเฉยต่อปัญหาเนื่องจากโปรแกรมเมอร์ไม่มีสิทธิ์ที่จะบ่นว่าเกิดอะไรขึ้น

ในทางทฤษฎีเมื่อเข้าสู่ดินแดน UB สิ่งที่สามารถเกิดขึ้นได้ (รวมถึงภูตที่บินออกจากจมูกของคุณ ) สิ่งที่ตามปกติคือผู้เขียนคอมไพเลอร์ไม่สนใจและสำหรับตัวแปรท้องถิ่นค่าจะเป็นอะไรก็ตามที่อยู่ในหน่วยความจำสแต็ค .

นอกจากนี้ยังหมายความว่าเนื้อหามักจะ "แปลก" แต่คงที่หรือสุ่มเล็กน้อยหรือเปลี่ยนแปลงเล็กน้อย แต่มีรูปแบบที่ชัดเจนชัดเจน (เช่นการเพิ่มค่าในแต่ละรอบซ้ำ)

แน่นอนคุณไม่สามารถคาดหวังได้ว่ามันเป็นเครื่องกำเนิดไฟฟ้าแบบสุ่มที่ดี


28

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

คอมไพเลอร์การเพิ่มประสิทธิภาพที่ดีควรใช้

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

และรวบรวมเป็น noop นี้เร็วกว่าทางเลือกอื่นอย่างแน่นอน มันมีข้อเสียที่จะไม่ทำอะไรเลย แต่นั่นเป็นข้อเสียของพฤติกรรมที่ไม่ได้กำหนด


3
มากขึ้นอยู่กับว่าจุดประสงค์ของคอมไพเลอร์คือความช่วยเหลือของโปรแกรมเมอร์ในการผลิตไฟล์ที่สามารถใช้งานได้ซึ่งตรงตามข้อกำหนดของโดเมนหรือว่าจุดประสงค์คือการสร้างไฟล์เอ็กซีคิวต์ที่มีประสิทธิภาพที่สุดซึ่งพฤติกรรมจะสอดคล้องกับข้อกำหนดขั้นต่ำของมาตรฐาน C คำนึงถึงพฤติกรรมดังกล่าวว่าจะตอบสนองวัตถุประสงค์ที่เป็นประโยชน์หรือไม่ ในเรื่องเกี่ยวกับวัตถุประสงค์ในอดีตการมีโค้ดใช้ค่าเริ่มต้นโดยพลการสำหรับ r, g, b หรือทริกเกอร์กับดักบั๊กหากใช้งานได้จริงจะมีประโยชน์มากกว่าการเปลี่ยนรหัสให้เป็น nop โดยคำนึงถึงวัตถุประสงค์หลัง ...
supercat

2
... คอมไพเลอร์ที่ดีที่สุดควรพิจารณาว่าอินพุตใดที่ทำให้เกิดวิธีการข้างต้นในการดำเนินการและกำจัดรหัสใด ๆ ที่จะเกี่ยวข้องเฉพาะเมื่อได้รับอินพุตดังกล่าว
supercat

1
@supercat หรือจุดประสงค์ของมันคือ C. ในการสร้างโปรแกรมที่มีประสิทธิภาพตามมาตรฐานในขณะที่ช่วยให้โปรแกรมเมอร์ค้นหาสถานที่ที่การปฏิบัติตามกฎระเบียบอาจไม่มีประโยชน์ คอมไพเลอร์สามารถตอบสนองวัตถุประสงค์การประนีประนอมนี้โดยการเปล่งวินิจฉัยมากกว่ามาตรฐานต้องเช่นของ -Wall -WextraGCC
Damian Yerrick

1
ว่าค่าที่ไม่ได้กำหนดไม่ได้หมายความว่าพฤติกรรมของรหัสโดยรอบจะไม่ได้กำหนด ผู้เรียบเรียงไม่มีควร noop ฟังก์ชั่นที่ การเรียกใช้ฟังก์ชันทั้งสองไม่ว่าจะรับอินพุตใดก็ตามจะต้องเรียกอย่างแน่นอน ต้องเรียกครั้งแรกด้วยตัวเลขสามจำนวนระหว่าง 0 ถึง 255 และต้องเรียกสองด้วยค่าจริงหรือเท็จ "การเพิ่มประสิทธิภาพคอมไพเลอร์ที่ดี" สามารถปรับฟังก์ชั่นพารามิเตอร์ให้เป็นค่าคงที่ตามอำเภอใจกำจัดตัวแปรทั้งหมด แต่นั่นก็ไกลที่สุดเท่าที่จะเป็นไปได้ (เช่นกัน
Dewi Morgan

@DewiMorgan - เนื่องจากฟังก์ชั่นที่เรียกว่าเป็นประเภท "set this parameter" พวกมันเกือบจะลดลงเป็น noops เมื่ออินพุตเหมือนกันกับค่าปัจจุบันของพารามิเตอร์ซึ่งคอมไพเลอร์มีอิสระที่จะถือว่าเป็นกรณี
จูลส์

18

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

void updateEffect(){}

ซึ่งเร็วกว่าลูปที่ถูกต้องของคุณอย่างแน่นอนและเนื่องจาก UB นั้นสอดคล้องกันอย่างสมบูรณ์แบบ


18

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


13

ตัวอย่างรหัสเฉพาะของคุณอาจจะไม่ทำในสิ่งที่คุณคาดหวัง ในขณะที่ทางเทคนิคการวนซ้ำแต่ละครั้งของการวนซ้ำจะสร้างตัวแปรท้องถิ่นสำหรับค่า r, g และ b แต่ในทางปฏิบัติมันเป็นพื้นที่หน่วยความจำเดียวกันที่แน่นอนบนสแต็ก ดังนั้นมันจะไม่ได้รับการสุ่มซ้ำกับการวนซ้ำแต่ละครั้งและคุณจะสิ้นสุดการกำหนดค่า 3 ค่าเดียวกันสำหรับแต่ละ 1,000 สีโดยไม่คำนึงว่าการสุ่ม r, g และ b เป็นอย่างไร

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


12

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


1
จุดดีมาก! มันอาจเป็นกลอุบายในทางปฏิบัติ แต่อันที่จริงต้องใช้โชค
ความหมายมีความสำคัญ

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

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

12

เลวจริงๆ! นิสัยไม่ดีผลไม่ดี พิจารณา:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

ถ้าฟังก์ชั่นA_Function_that_use_a_lot_the_Stack()ทำการเริ่มต้นเหมือนกันเสมอมันจะทิ้งสแต็กด้วยข้อมูลเดียวกัน ข้อมูลนั้นคือสิ่งที่เราได้รับupdateEffect(): คุณค่าเดียวกันเสมอ! .


11

ฉันทำการทดสอบอย่างง่าย ๆ และก็ไม่ได้สุ่มเลย

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

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


จุดดี. ผลลัพธ์ขึ้นอยู่กับว่าตัวสร้างหมายเลข "สุ่ม" นี้ถูกเรียกใช้ในรหัสใด มันค่อนข้างจะคาดเดาไม่ได้กว่าการสุ่ม
NO_NAME

10

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


7

มีสถานการณ์บางอย่างที่หน่วยความจำที่ไม่ได้กำหนดค่าเริ่มต้นสามารถอ่านได้อย่างปลอดภัยโดยใช้ประเภท "unsigned char *" [เช่นบัฟเฟอร์ที่ส่งคืนจากmalloc] โค้ดอาจอ่านหน่วยความจำดังกล่าวโดยไม่ต้องกังวลเกี่ยวกับคอมไพเลอร์ที่ปล่อยให้เกิดเหตุและมีบางครั้งที่อาจมีประสิทธิภาพมากกว่าที่จะมีการเตรียมโค้ดสำหรับหน่วยความจำอะไรก็ตามที่อาจมีมากกว่าเพื่อให้แน่ใจว่า ตัวอย่างทั่วไปของสิ่งนี้จะใช้memcpyในบัฟเฟอร์เริ่มต้นบางส่วนแทนที่จะคัดลอกองค์ประกอบทั้งหมดที่มีข้อมูลที่มีความหมาย)

แม้ว่าในกรณีเช่นนี้เราควรสันนิษฐานว่าหากมีการรวมกันของไบต์จะก่อกวนโดยเฉพาะการอ่านมันจะให้รูปแบบของไบต์นั้นเสมอ (และหากรูปแบบใดรูปแบบหนึ่งจะก่อกวนการผลิต แต่ไม่ใช่ในการพัฒนา รูปแบบจะไม่ปรากฏจนกว่ารหัสจะอยู่ในการผลิต)

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


2
"หากการรวมกันของไบต์ใด ๆ จะก่อกวนอย่างยิ่งการอ่านมันจะให้รูปแบบของไบต์นั้นเสมอ" - จนกว่าคุณจะเขียนโค้ดเพื่อรับมือกับรูปแบบนั้น ณ จุดนั้นจะไม่ก่อกวนอีกต่อไปและรูปแบบอื่นจะอ่านในอนาคต
Steve Jessop

@SteveJessop: แม่นยำ สายของฉันเกี่ยวกับการพัฒนาและการผลิตมีจุดมุ่งหมายเพื่อถ่ายทอดความคิดที่คล้ายกัน โค้ดไม่ควรสนใจสิ่งที่อยู่ในความทรงจำที่ไม่ได้กำหนดขอบเขตที่เกินความคิดที่คลุมเครือของ "การสุ่มอาจจะดี" หากพฤติกรรมของโปรแกรมได้รับผลกระทบจากเนื้อหาของหน่วยความจำที่ไม่ได้กำหนดค่าเริ่มต้นเนื้อหาของชิ้นส่วนที่ได้มาในอนาคตอาจได้รับผลกระทบจากสิ่งนั้น
supercat

5

อย่างที่คนอื่นพูดมันจะเร็ว แต่ไม่สุ่ม

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

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

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

นอกจากนี้ไม่มีอะไรบอกว่าคอมไพเลอร์ไม่ควรเริ่มต้นค่าเหล่านี้ - ดังนั้นคอมไพเลอร์ในอนาคตอาจทำเช่นนั้น

โดยทั่วไปแล้ว: ความคิดที่ดีไม่ควรทำ (เช่นการเพิ่มประสิทธิภาพระดับรหัสฉลาด "มาก" จริง ๆ ... )


2
คุณกำลังทำการคาดการณ์ที่แข็งแกร่งเกี่ยวกับสิ่งที่จะเกิดขึ้นแม้ว่าจะไม่มีการรับประกันใด ๆ เนื่องจาก UB มันก็ไม่เป็นความจริงในทางปฏิบัติ
usr

3

ตามที่คนอื่นได้กล่าวมาแล้วนี่เป็นพฤติกรรมที่ไม่ได้กำหนด ( UB ) แต่มันอาจ "ทำงาน"

ยกเว้นปัญหาที่ผู้อื่นพูดถึงแล้วฉันเห็นปัญหาอื่น (ข้อเสีย) - มันจะไม่ทำงานในภาษาอื่นที่ไม่ใช่ C และ C ++ ฉันรู้ว่าคำถามนี้เกี่ยวกับ C ++ แต่ถ้าคุณสามารถเขียนโค้ดซึ่งจะเป็นรหัส C ++ และ Java ที่ดีและไม่ใช่ปัญหาแล้วทำไมล่ะ บางทีบางวันใครบางคนจะต้องพอร์ตมันเป็นภาษาอื่นและค้นหาข้อผิดพลาดที่เกิดจาก"มายากล" UB แบบนี้แน่นอนจะเป็นฝันร้าย (โดยเฉพาะอย่างยิ่งสำหรับนักพัฒนา C / C ++ ที่ไม่มีประสบการณ์)

ที่นี่มีคำถามเกี่ยวกับ UB ที่คล้ายกันอีก แค่คิดว่าคุณกำลังพยายามหาบั๊กแบบนี้โดยไม่ทราบว่า UB นี้ หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับสิ่งแปลก ๆ ดังกล่าวใน C / C ++ อ่านคำตอบสำหรับคำถามจากการเชื่อมโยงและดูนี้ GREATสไลด์โชว์ มันจะช่วยให้คุณเข้าใจสิ่งที่อยู่ภายใต้ฝากระโปรงและวิธีการทำงาน มันไม่ได้เป็นเพียงแค่สไลด์โชว์ที่เต็มไปด้วย "เวทย์มนตร์" ฉันค่อนข้างแน่ใจว่าแม้แต่โปรแกรมเมอร์ C / c ++ ที่มีประสบการณ์ส่วนใหญ่ก็สามารถเรียนรู้ได้มากมายจากสิ่งนี้


3

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

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

https://stackoverflow.com/a/26170069/2724703

ดังนั้นหากเราเปลี่ยนรหัสข้างต้นและแทนที่ประเภทจริงด้วยautoโปรแกรมจะไม่ได้รวบรวม

void updateEffect(){
    for(int i=0;i<1000;i++){
        auto r;
        auto g;
        auto b;
        star[i].setColor(r%255,g%255,b%255);
        auto isVisible;
        star[i].setVisible(isVisible);
    }
}

3

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

มันจะทำให้คุณรู้สึกไม่มั่นคงที่จะรู้ว่าคุณกำลังใช้ "สุ่ม" เป็นตรรกะทางธุรกิจของคุณ ฉันไม่ได้ทำ


3

ใช้7757ทุกที่ที่คุณอยากใช้ตัวแปรที่ไม่มีค่าเริ่มต้น ฉันเลือกแบบสุ่มจากรายการหมายเลขเฉพาะ:

  1. มันถูกกำหนดพฤติกรรม

  2. มันรับประกันว่าจะไม่เป็น 0 เสมอ

  3. มันเป็นเรื่องสำคัญ

  4. มันน่าจะเป็นสถิติแบบสุ่มเป็นตัวแปรที่ไม่ได้กำหนด

  5. มันน่าจะเร็วกว่าตัวแปรที่ไม่กำหนดค่าเริ่มต้นเนื่องจากทราบค่าของมันในเวลารวบรวม


สำหรับการเปรียบเทียบดูผลลัพธ์ในคำตอบนี้: stackoverflow.com/a/31836461/2963099
Glenn Teitelbaum

1

มีความเป็นไปได้อีกข้อหนึ่งที่ต้องพิจารณา

คอมไพเลอร์สมัยใหม่ (ahem g ++) มีความฉลาดจนพวกเขาอ่านโค้ดของคุณเพื่อดูว่าคำสั่งใดมีผลต่อสถานะและสิ่งใดที่ไม่และถ้าคำสั่งนั้นรับประกันว่าจะไม่ส่งผลกระทบต่อสถานะ g ++ จะลบคำสั่งนั้น

ดังนั้นนี่คือสิ่งที่จะเกิดขึ้น g ++ แน่นอนจะเห็นว่าคุณกำลังอ่านดำเนินการทางคณิตศาสตร์ในการบันทึกสิ่งที่เป็นค่าขยะที่เป็นหลักซึ่งก่อให้เกิดขยะมากขึ้น เนื่องจากไม่มีการรับประกันว่าขยะใหม่จะมีประโยชน์มากกว่าขยะแบบเก่ามันก็จะหายไปกับลูปของคุณ แทรก!

วิธีนี้มีประโยชน์ แต่นี่คือสิ่งที่ฉันจะทำ รวม UB (พฤติกรรมที่ไม่ได้กำหนด) เข้ากับความเร็ว rand ()

แน่นอนลดrand()การกระทำของ แต่ผสมในเพื่อรวบรวมไม่ทำอะไรที่คุณไม่ต้องการ

และฉันจะไม่ยิงคุณ


ฉันคิดว่ามันยากมากที่จะเชื่อว่าผู้รวบรวมสามารถตัดสินใจว่าโค้ดของคุณกำลังทำอะไรโง่ ๆ และลบมันออกไป ฉันคาดหวังว่าจะเพิ่มประสิทธิภาพของรหัสที่ไม่ได้ใช้ออกไปเท่านั้นไม่ใช่รหัสที่ไม่เหมาะสม คุณมีกรณีทดสอบที่ทำซ้ำได้หรือไม่? ไม่ว่าจะด้วยวิธีใดข้อเสนอแนะของ UB นั้นอันตราย ยิ่งกว่านั้น GCC ไม่ใช่คอมไพเลอร์ที่เก่งกาจคนเดียวดังนั้นมันจึงไม่ยุติธรรมที่จะใช้ซิงเกิ้ล "modern"
underscore_d

-1

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

เห็นได้ชัดว่าการใช้งานนี้ไม่ได้เอกสารที่ดี แต่เพราะมีคนสังเกตเห็น Valgrind บ่นเกี่ยวกับการใช้ข้อมูลที่เตรียมและ "คงที่" มันก่อให้เกิดข้อผิดพลาดใน PRNG

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


1
สิ่งนี้จะขึ้นอยู่กับคอมไพเลอร์ของคุณซึ่งคาดว่าจะมีพฤติกรรมที่ไม่ได้กำหนดดังที่เราเห็นได้จากคำตอบเสียงดังของฉันในวันนี้จะไม่ทำสิ่งที่พวกเขาต้องการ
Shafik Yaghmour

6
OpenSSL นั้นใช้วิธีนี้ในการป้อนข้อมูลแบบเอนโทรปีไม่ได้บอกว่าดี หลังจากที่ทุกแหล่งเอนโทรปีเท่านั้นอื่น ๆ ที่พวกเขาใช้เป็นPID ไม่ใช่ค่าสุ่มที่ดีแน่นอน จากใครบางคนที่พึ่งพาแหล่งเอนโทรปีที่ไม่ดีเช่นนี้ฉันจะไม่คาดหวังการตัดสินใจที่ดีในแหล่งข้อมูลเอนโทรปีอื่น ๆ ฉันแค่หวังว่าคนที่ดูแล OpenSSL ในปัจจุบันนั้นจะสดใสกว่า
cmaster - คืนสถานะโมนิกา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.