การใช้“ ใหม่” ในโครงสร้างจัดสรรบนกองหรือกองหรือไม่


290

เมื่อคุณสร้างอินสแตนซ์ของคลาสด้วยnewโอเปอเรเตอร์หน่วยความจำจะได้รับการจัดสรรบนฮีป เมื่อคุณสร้างอินสแตนซ์ของโครงสร้างด้วยตัวnewดำเนินการที่หน่วยความจำได้รับการจัดสรรบนกองหรือกอง?

คำตอบ:


306

เอาล่ะมาดูกันว่าฉันจะทำให้เรื่องนี้ชัดเจนขึ้นได้ไหม

ประการแรกแอชพูดถูก: คำถามไม่เกี่ยวกับการจัดสรรตัวแปรประเภทค่า นั่นเป็นคำถามที่แตกต่าง - และอีกหนึ่งคำถามที่คำตอบไม่ใช่แค่ "ในสแต็ก" มันซับซ้อนกว่านั้น (และทำให้ซับซ้อนยิ่งขึ้นด้วย C # 2) ฉันมีบทความเกี่ยวกับหัวข้อและจะขยายออกไปหากมีการร้องขอ แต่ให้จัดการกับnewผู้ดำเนินการ

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

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

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


มีสองสถานการณ์ที่แตกต่างกันกับnewโอเปอเรเตอร์กับประเภทค่า: คุณสามารถเรียกตัวสร้างแบบไร้พารามิเตอร์ (เช่นnew Guid()) หรือตัวสร้างแบบพารามิเตอร์ (เช่นnew Guid(someString)) สิ่งเหล่านี้สร้าง IL ที่แตกต่างกันอย่างมีนัยสำคัญ เพื่อให้เข้าใจว่าทำไมคุณต้องเปรียบเทียบข้อมูลจำเพาะ C # และ CLI: ตาม C # ประเภทค่าทั้งหมดมีตัวสร้างแบบไม่มีพารามิเตอร์ ตามข้อกำหนด CLI ไม่มีชนิดค่าที่มีตัวสร้างแบบไม่มีพารามิเตอร์ (เรียกคอนสตรัคเตอร์ของประเภทค่าที่มีการสะท้อนบางครั้ง - คุณจะไม่พบพารามิเตอร์ที่ไม่มีพารามิเตอร์)

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

นอกจากนี้ยังสร้างความแตกต่างในสิ่งที่คุณจะทำกับค่าหลังจากที่คุณเริ่มต้นมัน IL ใช้สำหรับ

Guid localVariable = new Guid(someString);

ต่างจาก IL ที่ใช้สำหรับ:

myInstanceOrStaticVariable = new Guid(someString);

นอกจากนี้หากใช้ค่าเป็นค่ากลางเช่นการโต้แย้งในการเรียกใช้เมธอดสิ่งต่าง ๆ อีกเล็กน้อย เพื่อแสดงความแตกต่างเหล่านี้นี่คือโปรแกรมทดสอบสั้น ๆ ไม่แสดงความแตกต่างระหว่างตัวแปรสแตติกและตัวแปรอินสแตนซ์: IL จะแตกต่างกันระหว่างstfldและstsfldแต่นั่นคือทั้งหมด

using System;

public class Test
{
    static Guid field;

    static void Main() {}
    static void MethodTakingGuid(Guid guid) {}


    static void ParameterisedCtorAssignToField()
    {
        field = new Guid("");
    }

    static void ParameterisedCtorAssignToLocal()
    {
        Guid local = new Guid("");
        // Force the value to be used
        local.ToString();
    }

    static void ParameterisedCtorCallMethod()
    {
        MethodTakingGuid(new Guid(""));
    }

    static void ParameterlessCtorAssignToField()
    {
        field = new Guid();
    }

    static void ParameterlessCtorAssignToLocal()
    {
        Guid local = new Guid();
        // Force the value to be used
        local.ToString();
    }

    static void ParameterlessCtorCallMethod()
    {
        MethodTakingGuid(new Guid());
    }
}

นี่คือ IL สำหรับชั้นเรียนยกเว้นส่วนที่ไม่เกี่ยวข้อง (เช่น nops):

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object    
{
    // Removed Test's constructor, Main, and MethodTakingGuid.

    .method private hidebysig static void ParameterisedCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
        L_0010: ret     
    }

    .method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
    {
        .maxstack 2
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid    
        L_0003: ldstr ""    
        L_0008: call instance void [mscorlib]System.Guid::.ctor(string)    
        // Removed ToString() call
        L_001c: ret
    }

    .method private hidebysig static void ParameterisedCtorCallMethod() cil  managed    
    {   
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0011: ret     
    }

    .method private hidebysig static void ParameterlessCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
        L_0006: initobj [mscorlib]System.Guid
        L_000c: ret 
    }

    .method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        // Removed ToString() call
        L_0017: ret 
    }

    .method private hidebysig static void ParameterlessCtorCallMethod() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        L_0009: ldloc.0 
        L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0010: ret 
    }

    .field private static valuetype [mscorlib]System.Guid field
}

อย่างที่คุณเห็นมีคำสั่งต่าง ๆ มากมายที่ใช้เรียกตัวสร้าง:

  • newobj: จัดสรรค่าบนสแต็กเรียกตัวสร้างพารามิเตอร์ ใช้สำหรับค่ากลางเช่นการกำหนดให้กับเขตข้อมูลหรือใช้เป็นอาร์กิวเมนต์วิธี
  • call instance: ใช้ที่เก็บข้อมูลที่จัดสรรไว้แล้ว (ไม่ว่าจะอยู่ในสแต็กหรือไม่ก็ตาม) ใช้ในรหัสด้านบนเพื่อกำหนดให้กับตัวแปรท้องถิ่น หากตัวแปรโลคัลเดียวกันถูกกำหนดค่าหลายครั้งโดยใช้การnewเรียกหลายครั้งมันเพิ่งเริ่มต้นข้อมูลที่ด้านบนของค่าเดิม - มันจะไม่จัดสรรพื้นที่สแต็กเพิ่มเติมในแต่ละครั้ง
  • initobj: ใช้ที่เก็บข้อมูลที่จัดสรรไว้แล้วและเพียงแค่ลบข้อมูล สิ่งนี้ใช้สำหรับการเรียกพารามิเตอร์คอนสตรัคเตอร์แบบไร้พารามิเตอร์ทั้งหมดของเรารวมถึงสิ่งที่กำหนดให้กับตัวแปรโลคอล initobjสำหรับวิธีการเรียกเป็นตัวแปรท้องถิ่นกลางเป็นที่รู้จักได้อย่างมีประสิทธิภาพและคุ้มค่าโดยเช็ด

ฉันหวังว่าสิ่งนี้จะแสดงให้เห็นว่าหัวข้อซับซ้อนเพียงใดในขณะที่ส่องแสงเล็กน้อยในเวลาเดียวกัน ในประสาทสัมผัสบางแนวคิดทุกการเรียกเพื่อnewจัดสรรพื้นที่บนสแต็ก - แต่อย่างที่เราได้เห็นนั่นไม่ใช่สิ่งที่เกิดขึ้นจริงแม้แต่ในระดับ IL ฉันต้องการเน้นเฉพาะกรณี ใช้วิธีนี้:

void HowManyStackAllocations()
{
    Guid guid = new Guid();
    // [...] Use guid
    guid = new Guid(someBytes);
    // [...] Use guid
    guid = new Guid(someString);
    // [...] Use guid
}

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

แก้ไข: เพื่อให้ชัดเจนนี่เป็นจริงเฉพาะในบางกรณี ... โดยเฉพาะค่าguidจะไม่ปรากฏหากGuidConstructor ส่งข้อยกเว้นซึ่งเป็นสาเหตุที่คอมไพเลอร์ C # สามารถนำสแต็กสล็อตเดียวกันกลับมาใช้ใหม่ได้ ดูเอริค Lippert ของบล็อกโพสต์เกี่ยวกับประเภทค่าก่อสร้างสำหรับรายละเอียดเพิ่มเติมและกรณีที่มันไม่ได้ใช้

ฉันได้เรียนรู้มากมายในการเขียนคำตอบนี้ - โปรดขอคำอธิบายหากมีข้อใดไม่ชัดเจน!


1
จอนโค้ดตัวอย่าง HowManyStackAllocations นั้นดี แต่คุณสามารถเปลี่ยนเป็นใช้ Struct แทน Guid หรือเพิ่มตัวอย่าง Struct ใหม่ ฉันคิดว่าจะตอบคำถามเดิมของ @ kedar โดยตรง
Ash

9
Guid เป็นโครงสร้างอยู่แล้ว ดูmsdn.microsoft.com/en-us/library/system.guid.aspx ฉันจะไม่ได้เลือกชนิดการอ้างอิงสำหรับคำถามนี้ :)
จอนสกีต

1
จะเกิดอะไรขึ้นเมื่อคุณมีList<Guid>และเพิ่ม 3 ตัวนั้นเข้าไป? นั่นจะเป็นการจัดสรร 3 ครั้ง (IL เดียวกัน) แต่พวกเขาก็ยังคงมีมนต์ขลังอยู่
Arec Barrwin

1
@Ani: คุณขาดความจริงที่ว่าตัวอย่างของ Eric มีบล็อก try / catch - ดังนั้นหากมีข้อผิดพลาดเกิดขึ้นระหว่าง Constructor ของ Struct คุณจะต้องเห็นค่าก่อน Constructor ตัวอย่างของฉันไม่มีสถานการณ์เช่นนี้ - หากคอนสตรัคล้มเหลวด้วยข้อยกเว้นมันไม่สำคัญว่าค่าของguidการถูกเขียนทับเพียงครึ่งเดียวเนื่องจากไม่สามารถมองเห็นได้
Jon Skeet

2
@Ani: อันที่จริงเอริคเรียกสิ่งนี้ใกล้ด้านล่างของโพสต์ของเขา: "ตอนนี้แล้วประเด็นของเวสเนอร์คืออะไรใช่ในความเป็นจริงถ้ามันเป็นตัวแปรโลคอลที่ได้รับการจัดสรร (ไม่ใช่ฟิลด์ในการปิด) ในระดับเดียวกับ "ลอง" ซ้อนกันในขณะที่การเรียกใช้คอนสตรัคเตอร์แล้วเราจะไม่ผ่าน rigamarole นี้ในการสร้างชั่วคราวใหม่เริ่มต้นชั่วคราวและคัดลอกไปยังท้องถิ่นในกรณีเฉพาะ (และทั่วไป) ที่เราสามารถปรับให้เหมาะสม การสร้างชั่วคราวและสำเนาเพราะเป็นไปไม่ได้ที่โปรแกรม C # จะสังเกตเห็นความแตกต่าง! "
Jon Skeet

40

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

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

เช่นเดียวกันกับ structs ที่จัดสรรไว้ใน stack ยกเว้นว่า C # ต้องการตัวแปรท้องถิ่นทั้งหมดเพื่อตั้งค่าบางค่าก่อนที่จะใช้ดังนั้นคุณต้องเรียกใช้ตัวสร้างแบบกำหนดเองหรือตัวสร้างเริ่มต้น (ตัวสร้างที่ไม่มีพารามิเตอร์จะพร้อมใช้งานเสมอ structs)


13

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

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

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

มันอาจช่วยให้มองเข้าไปใน C ++ ซึ่งไม่มีความแตกต่างระหว่าง class / struct (มีชื่อที่คล้ายกันในภาษา แต่พวกเขาอ้างถึงการเข้าใช้งานเริ่มต้นของสิ่งต่าง ๆ เท่านั้น) เมื่อคุณโทรหาใหม่คุณจะได้รับตัวชี้ไปยังที่ตั้งฮีปในขณะที่ถ้าคุณมีการอ้างอิงที่ไม่ใช่ตัวชี้ ภายในวัตถุอื่น ๆ ala structs ใน C #


5

เช่นเดียวกับประเภทค่าทั้งหมด structs จะไปยังตำแหน่งที่ถูกประกาศเสมอ

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

แก้ไข:ฉันได้ตอบ mistankely ว่าพวกเขาเสมอไปในกอง นี่คือที่ไม่ถูกต้อง


"structs ไปที่ที่พวกเขาถูกประกาศเสมอ" นี่เป็นความเข้าใจผิดที่ทำให้สับสนเล็กน้อย เขตข้อมูล struct ในคลาสจะถูกวางลงใน "หน่วยความจำแบบไดนามิกเสมอเมื่ออินสแตนซ์ของประเภทนั้นถูกสร้าง" - Jeff Richter นี่อาจเป็นทางอ้อมบนฮีป แต่จะไม่เหมือนกับประเภทการอ้างอิงปกติเลย
Ash

ไม่ฉันคิดว่ามันเป็นตรงขวา - แม้ว่ามันจะไม่เหมือนกับชนิดการอ้างอิง ค่าของตัวแปรนั้นจะมีการประกาศอยู่ ค่าของตัวแปรประเภทการอ้างอิงคือการอ้างอิงแทนที่จะเป็นข้อมูลจริงนั่นคือทั้งหมด
Jon Skeet

โดยสรุปเมื่อใดก็ตามที่คุณสร้าง (ประกาศ) ประเภทค่าที่ใดก็ได้ในวิธีการที่มันจะถูกสร้างขึ้นบนสแต็กเสมอ
Ash

2
จอนคุณพลาดจุดของฉัน เหตุผลที่คำถามนี้ถูกถามครั้งแรกคือไม่ชัดเจนสำหรับนักพัฒนาหลายคน (ฉันรวมอยู่ด้วยจนกระทั่งฉันอ่าน CLR Via C #) ซึ่งมีการจัดสรรโครงสร้างถ้าคุณใช้โอเปอเรเตอร์ใหม่เพื่อสร้าง การบอกว่า "structs ไปที่ที่ประกาศเสมอ" ไม่ใช่คำตอบที่ชัดเจน
Ash

1
@Ash: ถ้าฉันมีเวลาฉันจะพยายามเขียนคำตอบเมื่อฉันไปทำงาน มันเป็นหัวข้อใหญ่เกินไปที่จะพยายามที่จะปกบนรถไฟแม้ว่า :)
จอนสกีต

4

ฉันอาจจะพลาดอะไรบางอย่างที่นี่ แต่ทำไมเราถึงสนใจการจัดสรร

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

ประเภทการอ้างอิงถูกส่งผ่านโดยการอ้างอิงและสามารถกลายพันธุ์ได้

แน่นอนว่าสตริงประเภทการอ้างอิงที่ไม่เปลี่ยนแปลงไม่แน่นอนนั้นเป็นสตริงที่ได้รับความนิยมมากที่สุด

รูปแบบอาร์เรย์ / การกำหนดค่าเริ่มต้น: ประเภทค่า -> หน่วยความจำศูนย์ [ชื่อ, ไปรษณีย์] [ชื่อ, ไปรษณีย์] ประเภทอ้างอิง -> หน่วยความจำศูนย์ -> null [อ้างอิง] [อ้างอิง]


3
ประเภทการอ้างอิงไม่ถูกส่งผ่านโดยการอ้างอิง - การอ้างอิงถูกส่งผ่านตามค่า มันแตกต่างกันมาก
Jon Skeet

2

classหรือstructประกาศเป็นเหมือนพิมพ์เขียวที่ใช้ในการสร้างอินสแตนซ์หรือวัตถุที่เวลาทำงาน หากคุณกำหนดclassหรือstructเรียกว่าบุคคลบุคคลคือชื่อของประเภท หากคุณประกาศและกำหนดค่าเริ่มต้นตัวแปร p ชนิดบุคคล p จะถูกกล่าวถึงว่าเป็นวัตถุหรืออินสแตนซ์ของบุคคล หลายกรณีของคนประเภทเดียวที่สามารถสร้างและแต่ละกรณีจะมีค่าแตกต่างกันในมันและpropertiesfields

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

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

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

สำหรับเพิ่มเติม ...


1

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


1

โครงสร้างได้รับการจัดสรรให้กับกอง นี่คือคำอธิบายที่เป็นประโยชน์:

structs

นอกจากนี้คลาสเมื่ออินสแตนซ์ภายใน. NET จัดสรรหน่วยความจำบนฮีปหรือพื้นที่หน่วยความจำสำรองของ. NET ในขณะที่ structs ให้ประสิทธิภาพมากขึ้นเมื่ออินสแตนซ์เนื่องจากการจัดสรรในกอง นอกจากนี้ควรสังเกตว่าการส่งพารามิเตอร์ภายใน structs ทำได้โดยค่า


5
สิ่งนี้จะไม่ครอบคลุมกรณีที่ struct เป็นส่วนหนึ่งของคลาส ณ จุดนั้นมันอาศัยอยู่บนฮีปพร้อมกับข้อมูลส่วนที่เหลือของวัตถุ
Jon Skeet

1
ใช่ แต่จริง ๆ แล้วมันเน้นและตอบคำถามที่ถูกถาม โหวตขึ้น
Ash

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