หน่วยความจำของตัวแปรโลคัลสามารถเข้าถึงได้นอกขอบเขตหรือไม่?


1029

ฉันมีรหัสต่อไปนี้

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

และรหัสกำลังทำงานโดยไม่มีข้อยกเว้นรันไทม์!

ผลลัพธ์ก็คือ 58

มันจะเป็นอย่างไร หน่วยความจำของตัวแปรโลคัลไม่สามารถเข้าถึงได้นอกฟังก์ชั่นหรือไม่


14
สิ่งนี้จะไม่ได้รวบรวมตามที่เป็นอยู่; ถ้าคุณแก้ไขธุรกิจ nonforming, GCC จะยังคงเตือนaddress of local variable ‘a’ returned; การแสดง valgrindInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
sehe

76
@ Serge: ย้อนกลับไปในวัยเยาว์ของฉันฉันเคยทำงานกับรหัส zero-ring จำนวนหนึ่งที่ทำงานบนระบบปฏิบัติการ Netware ที่เกี่ยวข้องกับการเคลื่อนที่รอบตัวชี้สแต็คอย่างชาญฉลาดในลักษณะที่ระบบปฏิบัติการไม่ได้ถูกลงโทษ ฉันรู้ว่าเมื่อฉันทำผิดเพราะกองมักจะจบลงด้วยการซ้อนทับหน่วยความจำหน้าจอและฉันก็สามารถดูไบต์ที่ถูกเขียนลงบนจอแสดงผล วันนี้คุณไม่สามารถหนีจากสิ่งเหล่านี้ได้
Eric Lippert

23
ฮ่า ๆ. ฉันต้องอ่านคำถามและคำตอบก่อนที่ฉันจะเข้าใจว่าปัญหาอยู่ตรงไหน นั่นเป็นคำถามเกี่ยวกับขอบเขตการเข้าถึงของตัวแปรหรือไม่? คุณไม่ได้ใช้ 'a' นอกฟังก์ชั่นของคุณ และนั่นคือทั้งหมดที่มีให้มัน การอ้างอิงหน่วยความจำบางอย่างเป็นหัวข้อที่แตกต่างอย่างสิ้นเชิงจากขอบเขตตัวแปร
erikbwork

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

16
@Joel: ถ้าคำตอบที่นี่ดีก็ควรจะถูกรวมเข้ากับคำถามที่เก่ากว่าซึ่งนี่คือ dupe ไม่ใช่วิธีอื่น และคำถามนี้จริง ๆ แล้วเป็นคำถามล่อแหลมของคำถามอื่น ๆ ที่เสนอที่นี่แล้วบางคำถาม (แม้ว่าบางคำถามที่เสนอนั้นเหมาะสมกว่าคำถามอื่น ๆ ) โปรดทราบว่าฉันคิดว่าคำตอบของ Eric นั้นดี (ในความเป็นจริงผมตั้งค่าสถานะคำถามนี้ตอบสำหรับการรวมเป็นหนึ่งในคำถามที่เก่าเพื่อที่จะกอบกู้คำถามเก่า.)
เอสบีไอ

คำตอบ:


4801

มันจะเป็นอย่างไร หน่วยความจำของตัวแปรโลคัลไม่สามารถเข้าถึงได้นอกฟังก์ชั่นหรือไม่

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

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

นั่นเป็นอย่างไร เนื้อหาของลิ้นชักห้องพักของโรงแรมไม่สามารถเข้าถึงได้หากคุณยังไม่ได้เช่าห้อง

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

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

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

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

c ++ ไม่ใช่ภาษาที่ปลอดภัย มันจะช่วยให้คุณมีความสุขที่จะทำลายกฎของระบบ หากคุณพยายามทำสิ่งผิดกฎหมายและโง่เขลาเหมือนกลับไปที่ห้องที่คุณไม่ได้รับอนุญาตให้เข้าไปในและค้นหาผ่านโต๊ะที่อาจไม่ได้อยู่ที่นั่นอีกต่อไป C ++ จะไม่หยุดคุณ ภาษาที่ปลอดภัยกว่า C ++ แก้ปัญหานี้ด้วยการ จำกัด พลังงานของคุณ - โดยการควบคุมปุ่มอย่างเข้มงวดตัวอย่างเช่น

UPDATE

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

ฉันคิดว่ามันน่าจะอัปเดตเล็กน้อยด้วยความคิดด้านเทคนิคเพิ่มเติม

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

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

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

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

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

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

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

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

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

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

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

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

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

สำหรับการอ่านเพิ่มเติม:

  • เกิดอะไรขึ้นถ้า C # อนุญาตให้มีการอ้างอิงที่กลับมา? บังเอิญว่าเป็นเรื่องของการโพสต์บล็อกในวันนี้:

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

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

    https://ericlippert.com/tag/memory-management/


56
@muntoo: น่าเสียดายที่มันไม่เหมือนกับระบบปฏิบัติการที่ส่งเสียงไซเรนเตือนก่อนที่จะทำการ decommits หรือยกเลิกการจัดสรรหน้าหน่วยความจำเสมือน หากคุณกำลังล้อเล่นกับหน่วยความจำนั้นเมื่อคุณไม่ได้เป็นเจ้าของอีกต่อไประบบปฏิบัติการจะอยู่ในสิทธิ์ในการจัดการกระบวนการทั้งหมดเมื่อคุณสัมผัสหน้า deallocated บูม!
Eric Lippert

83
@ ไคล์: โรงแรมที่ปลอดภัยเท่านั้นที่ทำเช่นนั้น โรงแรมที่ไม่ปลอดภัยจะได้รับกำไรที่วัดได้โดยไม่ต้องเสียเวลากับคีย์การเขียนโปรแกรม
Alexander Torstling

498
@cyberguijarro: C ++ นั้นไม่ปลอดภัยสำหรับหน่วยความจำ แต่เป็นความจริง มันไม่ใช่ "การทุบตี" อะไรเลย เช่นถ้าฉันพูดว่า "C ++ เป็นความผิดที่น่ากลัวของคุณลักษณะที่ซับซ้อนและซับซ้อนเกินกว่าที่ระบุไว้ด้านบนของโมเดลหน่วยความจำที่เปราะบางและอันตรายและฉันรู้สึกขอบคุณทุกวันที่ฉันไม่ได้ทำงานเพื่อสติของตัวเอง" ที่จะทุบตี C ++ ชี้ให้เห็นว่าหน่วยความจำที่ไม่ปลอดภัยกำลังอธิบายว่าทำไมโปสเตอร์ต้นฉบับจึงเห็นปัญหานี้ เป็นการตอบคำถามไม่ใช่การแก้ไข
Eric Lippert

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

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

276

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

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

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

กล่าวโดยย่อ: วิธีนี้ใช้ไม่ได้ผล แต่บางครั้งจะเกิดขึ้นโดยบังเอิญ


152

เนื่องจากพื้นที่เก็บข้อมูลยังไม่ถูกเหยียบย่ำ อย่าพึ่งพาพฤติกรรมนั้น


1
มนุษย์นั่นคือการรอคอยที่ยาวนานที่สุดในการแสดงความคิดเห็นตั้งแต่ "ความจริงคืออะไร? อาจเป็นคัมภีร์ไบเบิลของกิเดโอนในลิ้นชักโรงแรมนั้น แล้วเกิดอะไรขึ้นกับพวกเขา สังเกตว่าพวกเขาไม่ได้อยู่ในลอนดอนอย่างน้อยที่สุด ฉันเดาว่าภายใต้กฎหมายว่าด้วยความเสมอภาคคุณจะต้องมีห้องสมุดของสถานที่ทางศาสนา
Rob Kent

ฉันสาบานได้ว่าฉันเขียนมานานแล้ว แต่มันโผล่ขึ้นมาเมื่อเร็ว ๆ นี้และพบว่าคำตอบของฉันไม่ได้อยู่ที่นั่น ตอนนี้ฉันต้องไปหาคำพาดพิงของคุณข้างต้นตามที่ฉันคาดหวังว่าฉันจะขบขันเมื่อฉันทำ>. <
msw

1
ฮ่าฮ่า ฟรานซิสเบคอนหนึ่งในนักเขียนเรียงความที่ยิ่งใหญ่ที่สุดของสหราชอาณาจักรซึ่งบางคนสงสัยว่าจะเขียนบทละครของเชคสเปียร์เพราะพวกเขาไม่สามารถยอมรับได้ว่าเด็กโรงเรียนมัธยมจากประเทศลูกชายของโกลเวอร์อาจเป็นอัจฉริยะ นั่นคือระบบชั้นเรียนภาษาอังกฤษ พระเยซูตรัสว่า 'เราเป็นความจริง' oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
Rob Kent

84

นอกจากนี้ยังมีคำตอบทั้งหมด:

ถ้าคุณทำอะไรแบบนั้น:

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

ผลลัพธ์อาจจะเป็น: 7

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


2
ง่าย ๆ แต่เป็นตัวอย่างที่ดีในการทำความเข้าใจทฤษฎีสแต็กพื้นฐานเพียงแค่การทดสอบหนึ่งครั้งโดยประกาศว่า "int a = 5;" ใน foo () เป็น "static int a = 5;" สามารถใช้เพื่อทำความเข้าใจขอบเขตและเวลาชีวิตของตัวแปรแบบคงที่
ควบคุม

15
-1 "สำหรับอาจจะเป็น 7 " คอมไพเลอร์อาจลงทะเบียน a ในบู อาจลบออกเพราะไม่จำเป็น มีโอกาสที่ดีที่ * p จะเป็นไม่เป็น 5แต่นั่นไม่ได้หมายความว่ามีเหตุผลที่ดีใด ๆ โดยเฉพาะอย่างยิ่งว่าทำไมมันจะอาจจะ 7
Matt

2
มันเรียกว่าพฤติกรรมที่ไม่ได้กำหนด!
Francis Cugler

เหตุใดและการนำมาbooใช้ซ้ำได้fooอย่างไร ไม่ใช่ฟังก์ชั่นที่แยกออกจากกัน แต่ฉันยังได้รับขยะที่ใช้รหัสนี้ใน Visual Studio 2015
ampawd

1
@ampawd เป็นเกือบปีแล้ว แต่ไม่มี "stack function" ไม่ได้แยกออกจากกัน บริบทมีสแต็ก บริบทที่ใช้สแต็คของมันที่จะเข้าสู่หลักแล้วลงมามีอยู่แล้วก้าวลงไปสู่ foo() และทั้งสองป้อนด้วยตัวชี้สแต็คที่ตำแหน่งเดียวกัน อย่างไรก็ตามนี่ไม่ใช่พฤติกรรมที่ควรเชื่อถือได้ 'เนื้อหา' อื่น ๆ (เช่นการขัดจังหวะหรือระบบปฏิบัติการ) สามารถใช้สแต็กระหว่างการเรียกและแก้ไขเนื้อหา ...boo()Foo()Boo()boo()foo()
Russ Schultz

72

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


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

67

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

unsigned int q = 123456;

*(double*)(q) = 1.2;

ที่นี่ฉันแค่รักษา 123456 เป็นที่อยู่ของสองและเขียนถึงมัน สิ่งต่าง ๆ สามารถเกิดขึ้นได้:

  1. qdouble p; q = &p;ในความเป็นจริงอาจจะอย่างแท้จริงจะเป็นที่อยู่ที่ถูกต้องของคู่เช่น
  2. q อาจชี้ไปที่หน่วยความจำที่จัดสรรไว้และฉันก็เขียนทับ 8 ไบต์ตรงนั้น
  3. q จุดภายนอกหน่วยความจำที่จัดสรรและตัวจัดการหน่วยความจำของระบบปฏิบัติการจะส่งสัญญาณข้อผิดพลาดการแบ่งกลุ่มไปยังโปรแกรมของฉันทำให้รันไทม์ยุติลง
  4. คุณชนะลอตเตอรี

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

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


9
ฉันเพิ่งจะเขียนโปรแกรมในขณะนี้ที่ยังคงใช้งานโปรแกรมนี้เพื่อให้4) I win the lottery
Aidiakapi

29

คุณรวบรวมโปรแกรมของคุณโดยใช้เครื่องมือเพิ่มประสิทธิภาพหรือไม่ foo()ฟังก์ชั่นค่อนข้างง่ายและอาจได้รับการ inlined หรือแทนที่ด้วยรหัสผล

แต่ฉันเห็นด้วยกับ Mark B ว่าพฤติกรรมที่เกิดขึ้นนั้นไม่ได้กำหนดไว้


นั่นคือเดิมพันของฉัน เครื่องมือเพิ่มประสิทธิภาพทิ้งการเรียกใช้ฟังก์ชัน
Erik Aronesty

9
ที่ไม่จำเป็น เนื่องจากไม่มีฟังก์ชั่นใหม่ถูกเรียกหลังจาก foo () ฟังก์ชั่น local stack frame ก็ยังไม่ถูกเขียนทับ เพิ่มการเรียกใช้ฟังก์ชันอื่นหลังจาก foo () และ5จะเปลี่ยน ...
Tomas

ฉันรันโปรแกรมด้วย GCC 4.8 แทนที่ cout ด้วย printf (และรวมถึง stdio) เตือนอย่างถูกต้อง "คำเตือน: ที่อยู่ของตัวแปรท้องถิ่น 'a' ส่งคืน [-Wreturn-local-addr]" เอาต์พุต 58 ที่ไม่มีการออปติไมซ์และ 08 กับ -O3 Strangely P มีที่อยู่แม้ว่าค่าจะเป็น 0 ฉันคาดหวังว่า NULL (0) เป็นที่อยู่
kevinf

23

ปัญหาของคุณมีอะไรจะทำอย่างไรกับขอบเขต ในรหัสที่คุณแสดงฟังก์ชั่นmainไม่เห็นชื่อในการทำงานfooเพื่อให้คุณไม่สามารถเข้าถึงaใน foo โดยตรงกับนี้fooชื่อนอก

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


ฉันจำได้จากสำเนาเก่าของโปรแกรม Turbo C สำหรับ IBMซึ่งฉันเคยเล่นด้วยวิธีย้อนกลับเมื่อไหร่วิธีจัดการหน่วยความจำกราฟิกโดยตรงและโครงร่างของหน่วยความจำวิดีโอโหมดข้อความของ IBM ได้รับการอธิบายอย่างละเอียด แน่นอนว่าระบบที่ใช้รหัสนั้นกำหนดไว้อย่างชัดเจนว่าการเขียนไปยังที่อยู่เหล่านั้นมีความหมายอย่างไรตราบใดที่คุณไม่ต้องกังวลเกี่ยวกับการพกพาไปยังระบบอื่นทุกอย่างก็ใช้ได้ IIRC ตัวชี้เพื่อโมฆะเป็นหัวข้อหลักในหนังสือเล่มนั้น
CVn

@Michael Kjörling: แน่นอน! ผู้คนชอบทำงานสกปรกบ้างเป็นครั้งคราว;)
Chang Peng

18

คุณเพิ่งส่งคืนที่อยู่หน่วยความจำ แต่อาจได้รับข้อผิดพลาด

ใช่ถ้าคุณพยายามอ้างถึงที่อยู่หน่วยความจำคุณจะมีพฤติกรรมที่ไม่ได้กำหนด

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}

ผมไม่เห็นด้วย: coutมีปัญหาก่อน *aชี้ไปที่หน่วยความจำที่ไม่ได้ถูกจัดสรร (อิสระ) แม้ว่าคุณจะไม่ได้เงียบมันก็ยังคงเป็นอันตราย (และน่าจะเป็นการหลอกลวง)
2010

@ereOn: ฉันชี้แจงเพิ่มเติมสิ่งที่ฉันหมายถึงปัญหา แต่ไม่เป็นอันตรายในแง่ของรหัส c ++ ที่ถูกต้อง แต่มันเป็นอันตรายในแง่ของโอกาสที่ผู้ใช้ทำผิดและจะทำสิ่งที่ไม่ดี ตัวอย่างเช่นคุณกำลังพยายามดูว่าสแต็คเติบโตขึ้นอย่างไรและคุณสนใจเฉพาะค่าที่อยู่และจะไม่ตรวจสอบซ้ำ
Brian R. Bondy

18

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


18

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

สำหรับตัวอย่างของพฤติกรรมที่น่ากลัวที่คุณน่าจะลองได้จากตัวอย่างนี้:

int *a()
{
   int x = 5;
   return &x;
}

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

สิ่งนี้จะพิมพ์ออกมา "y = 123" แต่ผลลัพธ์ของคุณอาจแตกต่างกันไป (จริง ๆ !) ตัวชี้ของคุณกำลังอุดตันตัวแปรท้องถิ่นอื่น ๆ ที่ไม่เกี่ยวข้อง


18

ใส่ใจกับคำเตือนทั้งหมด ไม่เพียง แต่แก้ไขข้อผิดพลาด
GCC แสดงคำเตือนนี้

คำเตือน: ที่อยู่ของตัวแปรโลคอล 'a' ถูกส่งคืน

นี่คือพลังของ C ++ คุณควรใส่ใจเรื่องความจำ ด้วยการ-Werrorตั้งค่าสถานะคำเตือนนี้เกิดข้อผิดพลาดและตอนนี้คุณต้องตรวจแก้จุดบกพร่อง


17

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


16

คุณเรียกใช้พฤติกรรมที่ไม่ได้กำหนดจริง ๆ

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

ดังนั้นคุณไม่ได้ปรับเปลี่ยนaแต่เป็นตำแหน่งหน่วยความจำที่aครั้งหนึ่งเคยเป็น ความแตกต่างนี้คล้ายกับความแตกต่างระหว่างการหยุดทำงานและไม่หยุดทำงาน


14

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

อย่างไรก็ตามนี่เป็นพฤติกรรมที่ไม่ได้กำหนดและคุณไม่ควรใช้มันในการทำงาน!


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

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

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

14

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

สิ่งที่คุณทำอธิบายว่าเป็นพฤติกรรมที่ไม่ได้กำหนด ไม่สามารถคาดการณ์ผลลัพธ์ได้


12

สิ่งที่มีเอาต์พุตคอนโซลที่ถูกต้อง (?) สามารถเปลี่ยนแปลงได้อย่างมากหากคุณใช้ :: printf แต่ไม่ใช่ cout คุณสามารถเล่นกับ debugger ภายในโค้ดด้านล่าง (ทดสอบกับ x86, 32-bit, MSVisual Studio):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

5

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

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

ขอยกตัวอย่างโลกแห่งความจริง:

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


4

เป็นวิธี 'สกปรก' ในการใช้ที่อยู่หน่วยความจำ เมื่อคุณส่งคืนที่อยู่ (ตัวชี้) คุณจะไม่ทราบว่ามันอยู่ในขอบเขตภายในของฟังก์ชันหรือไม่ เป็นเพียงที่อยู่ หลังจากที่คุณเรียกใช้ฟังก์ชั่น 'foo' ที่อยู่ (ตำแหน่งหน่วยความจำ) ของ 'a' ได้ถูกจัดสรรไปแล้วในหน่วยความจำที่สามารถกำหนดแอดเดรสได้ของแอปพลิเคชันของคุณ (กระบวนการ) หลังจากฟังก์ชั่น 'foo' กลับมาแล้วที่อยู่ของ 'a' อาจถือได้ว่าเป็น 'สกปรก' แต่มันอยู่ที่นั่นไม่ได้ทำความสะอาดหรือรบกวน / แก้ไขโดยการแสดงออกในส่วนอื่น ๆ ของโปรแกรม (ในกรณีนี้อย่างน้อย) คอมไพเลอร์ AC / C ++ ไม่ได้หยุดคุณจากการเข้าถึง 'สกปรก' (อาจเตือนคุณถ้าคุณสนใจ)


1

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

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

ซึ่งหมายความว่าคุณกำลังทำสิ่งที่เลวร้ายมากเพราะคุณกำลังส่งที่อยู่หน่วยความจำไปยังตัวชี้ซึ่งไม่น่าเชื่อถือเลย

ลองพิจารณาตัวอย่างนี้แทนและทดสอบ:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

ไม่เหมือนกับตัวอย่างของคุณด้วยตัวอย่างนี้คุณ:

  • การจัดสรรหน่วยความจำสำหรับ int ในฟังก์ชั่นท้องถิ่น
  • ที่อยู่หน่วยความจำนั้นยังใช้งานได้เมื่อฟังก์ชั่นหมดอายุ (ทุกคนจะไม่ถูกลบ)
  • ที่อยู่หน่วยความจำเชื่อถือได้ (บล็อกหน่วยความจำนั้นไม่ถือว่าว่างดังนั้นจะไม่ถูกเขียนทับจนกว่าจะถูกลบ)
  • ควรลบที่อยู่หน่วยความจำเมื่อไม่ได้ใช้ (ดูการลบในตอนท้ายของโปรแกรม)

คุณเพิ่มบางสิ่งที่ยังไม่ได้ครอบคลุมโดยคำตอบที่มีอยู่หรือไม่? และกรุณาอย่าใช้ตัวชี้ดิบnew/
การแข่งขัน Lightness ใน Orbit

1
ผู้ถามใช้ตัวชี้แบบดิบ ฉันทำตัวอย่างซึ่งสะท้อนให้เห็นอย่างชัดเจนตัวอย่างที่เขาทำเพื่อให้เขาเห็นความแตกต่างระหว่างตัวชี้ที่ไม่เป็นสนิมและเชื่อถือได้ จริงๆแล้วมีคำตอบอื่นที่คล้ายกับของฉัน แต่ใช้ strcpy ซึ่ง IMHO อาจชัดเจนน้อยกว่า coder สามเณรกว่าตัวอย่างของฉันที่ใช้ใหม่
Nobun

newพวกเขาไม่ได้ใช้ newคุณกำลังสอนพวกเขาไปใช้ newแต่คุณไม่ควรใช้
การแข่งขัน Lightness ใน Orbit

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

เมื่อไหร่ที่ฉันพูดอย่างนั้น? ไม่จะเป็นการดีกว่าถ้าใช้ตัวชี้สมาร์ทเพื่อระบุความเป็นเจ้าของทรัพยากรอ้างอิงอย่างถูกต้อง อย่าใช้newในปี 2019 (เว้นแต่คุณจะเขียนรหัสห้องสมุด) และไม่สอนผู้มาใหม่ให้ทำเช่นนั้น! ไชโย
การแข่งขัน Lightness ใน Orbit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.