ทำไมโครงสร้างไม่รองรับการสืบทอด


129

ฉันรู้ว่าโครงสร้างใน. NET ไม่รองรับการสืบทอด แต่ไม่ชัดเจนว่าทำไมจึงถูก จำกัด ด้วยวิธีนี้

เหตุผลทางเทคนิคใดที่ป้องกันไม่ให้โครงสร้างรับช่วงจากโครงสร้างอื่น ๆ


6
ฉันไม่ได้ตายเพราะฟังก์ชันนี้ แต่ฉันสามารถนึกถึงบางกรณีที่การสืบทอดโครงสร้างจะมีประโยชน์: คุณอาจต้องการขยายโครงสร้าง Point2D กับโครงสร้าง Point3D ด้วยการสืบทอดคุณอาจต้องการสืบทอดจาก Int32 เพื่อ จำกัด ค่า ระหว่าง 1 ถึง 100 คุณอาจต้องการสร้าง type-def ที่สามารถมองเห็นได้ในหลายไฟล์ (เคล็ดลับการใช้ typeA = typeB มีขอบเขตไฟล์เท่านั้น) ฯลฯ
Juliet

4
คุณอาจต้องการอ่านstackoverflow.com/questions/1082311/…ซึ่งจะอธิบายเพิ่มเติมเกี่ยวกับโครงสร้างและเหตุใดจึงควร จำกัด ขนาดที่แน่นอน หากคุณต้องการใช้การสืบทอดในโครงสร้างคุณควรใช้คลาส
Justin

1
และคุณอาจต้องการอ่านstackoverflow.com/questions/1222935/…ในเชิงลึกว่าทำไมจึงไม่สามารถทำได้ในแพลตฟอร์ม dotNet พวกเขาเย็นชาทำให้เป็นวิธี C ++ โดยมีปัญหาเดียวกันซึ่งอาจเป็นหายนะสำหรับแพลตฟอร์มที่มีการจัดการ
Dykam

@Justin Classes มีต้นทุนด้านประสิทธิภาพที่โครงสร้างสามารถหลีกเลี่ยงได้ และในการพัฒนาเกมที่สำคัญจริงๆ ดังนั้นในบางกรณีคุณไม่ควรใช้ชั้นเรียนหากสามารถช่วยได้
Gavin Williams

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

คำตอบ:


121

ประเภทค่าเหตุผลไม่สามารถรองรับการสืบทอดได้เนื่องจากอาร์เรย์

ปัญหาคือด้วยเหตุผลด้านประสิทธิภาพและ GC อาร์เรย์ของประเภทค่าจะถูกเก็บไว้ "แบบอินไลน์" ตัวอย่างเช่นnew FooType[10] {...}หากFooTypeเป็นชนิดการอ้างอิงวัตถุ 11 ชิ้นจะถูกสร้างขึ้นบนฮีปที่มีการจัดการ (หนึ่งรายการสำหรับอาร์เรย์และ 10 สำหรับอินสแตนซ์แต่ละประเภท) ถ้าFooTypeเป็นประเภทค่าแทนจะมีเพียงอินสแตนซ์เดียวเท่านั้นที่ถูกสร้างขึ้นบนฮีปที่มีการจัดการ - สำหรับอาร์เรย์เอง (เนื่องจากค่าอาร์เรย์แต่ละค่าจะถูกจัดเก็บ "แบบอินไลน์" ด้วยอาร์เรย์)

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

พิจารณารหัสหลอก C # นี้:

struct Base
{
    public int A;
}

struct Derived : Base
{
    public int B;
}

void Square(Base[] values)
{
  for (int i = 0; i < values.Length; ++i)
      values [i].A *= 2;
}

Derived[] v = new Derived[2];
Square (v);

ตามกฎการแปลงปกติ a Derived[]สามารถแปลงเป็น a Base[](ดีขึ้นหรือแย่ลง) ดังนั้นหากคุณ s / struct / class / g สำหรับตัวอย่างข้างต้นมันจะคอมไพล์และทำงานตามที่คาดไว้โดยไม่มีปัญหา แต่ถ้าBaseและDerivedเป็นชนิดค่าและอาร์เรย์เก็บค่าไว้ในบรรทัดแสดงว่าเรามีปัญหา

เรามีปัญหาเพราะSquare()ไม่รู้อะไรเลยDerivedมันจะใช้เฉพาะเลขคณิตตัวชี้เพื่อเข้าถึงแต่ละองค์ประกอบของอาร์เรย์โดยเพิ่มขึ้นด้วยจำนวนคงที่ ( sizeof(A)) การชุมนุมจะคลุมเครือเช่น:

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

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

ดังนั้นหากสิ่งนี้เกิดขึ้นจริงเราจะมีปัญหาหน่วยความจำเสียหาย โดยเฉพาะภายในSquare(), values[1].A*=2จะจริงจะแก้ไขvalues[0].B!

พยายามแก้ปัญหาว่า !


11
วิธีแก้ปัญหาที่สมเหตุสมผลคือการไม่อนุญาตให้ใช้แบบฟอร์มการร่าย Base [] เป็น Detived [] เช่นเดียวกับการแคสต์จาก short [] ถึง int [] เป็นสิ่งต้องห้ามแม้ว่าการแคสต์จาก short ไปยัง int จะทำได้
Niki

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

4
ใช่ แต่นั่นเป็นสิ่งที่ "สมเหตุสมผล" เนื่องจากการแปลงอาร์เรย์มีไว้สำหรับการแปลงโดยนัยไม่ใช่การแปลงอย่างชัดเจน สั้นถึง int เป็นไปได้ แต่ต้องใช้การร่ายดังนั้นจึงสมเหตุสมผลที่จะไม่สามารถแปลง short [] เป็น int [] (ย่อมาจากรหัสการแปลงเช่น 'a.Select (x => (int) x) .ToArray ( )) หากรันไทม์ไม่อนุญาตให้ร่ายจาก Base เป็น Derived มันจะเป็น "หูด" เนื่องจาก IS อนุญาตสำหรับประเภทการอ้างอิง ดังนั้นเราจึงมี "หูด" ที่แตกต่างกันสองอย่างที่เป็นไปได้ - ห้ามไม่ให้มีการสืบทอดโครงสร้างหรือห้ามการแปลงอาร์เรย์ที่ได้มาเป็นอาร์เรย์ของฐาน
jonp

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

2
จำเป็นต้องเปลี่ยนชื่อของฟังก์ชันจาก "สแควร์" เป็น "คู่"
John

70

ลองนึกภาพโครงสร้างที่รองรับการถ่ายทอดทางพันธุกรรม จากนั้นประกาศ:

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

จะหมายถึงตัวแปร struct ไม่มีขนาดคงที่และนั่นคือเหตุผลที่เรามีประเภทอ้างอิง

ยิ่งไปกว่านั้นให้พิจารณาสิ่งนี้:

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?

5
C ++ ตอบสิ่งนี้โดยการแนะนำแนวคิดของ 'การแบ่งส่วน' ซึ่งเป็นปัญหาที่แก้ไขได้ เหตุใดจึงไม่ควรสนับสนุนการสืบทอดโครงสร้าง
jonp

1
พิจารณาอาร์เรย์ของโครงสร้างที่สืบทอดได้และจำไว้ว่า C # เป็นภาษาที่มีการจัดการ (หน่วยความจำ) การแบ่งส่วนหรือตัวเลือกที่คล้ายคลึงกันจะสร้างความเสียหายให้กับปัจจัยพื้นฐานของ CLR
Kenan EK

4
@jonp: แก้ได้ใช่มะ เป็นที่น่าพอใจ? นี่คือการทดลองทางความคิด: สมมติว่าคุณมีคลาสพื้นฐาน Vector2D (x, y) และคลาสที่ได้รับ Vector3D (x, y, z) ทั้งสองคลาสมีคุณสมบัติ Magnitude ซึ่งคำนวณ sqrt (x ^ 2 + y ^ 2) และ sqrt (x ^ 2 + y ^ 2 + z ^ 2) ตามลำดับ ถ้าคุณเขียน 'Vector3D a = Vector3D (5, 10, 15); Vector2D b = a; 'สิ่งที่' a.Magnitude == b.Magnitude 'ควรกลับมา? ถ้าเราเขียน 'a = (Vector3D) b' แล้ว a.Magnitude มีค่าเท่ากันก่อนการกำหนดเหมือนที่ทำหลังหรือไม่? นักออกแบบ. NET อาจพูดกับตัวเองว่า "ไม่เราจะไม่มีสิ่งนั้น"
Juliet

1
เพียงเพราะปัญหาสามารถแก้ไขได้ไม่ได้หมายความว่าควรแก้ไข บางครั้งก็เป็นการดีที่สุดที่จะหลีกเลี่ยงสถานการณ์ที่เกิดปัญหาขึ้น
Dan Diplo

@ kek444: การFooสืบทอดโครงสร้างBarไม่ควรอนุญาตให้Fooกำหนด a ให้กับ a Barแต่การประกาศโครงสร้างด้วยวิธีนี้อาจทำให้เกิดผลประโยชน์สองสามประการ: (1) สร้างสมาชิกที่มีชื่อพิเศษBarเป็นรายการแรกในFooและFooรวมไว้ด้วย ชื่อของสมาชิกที่นามแฝงให้กับสมาชิกผู้ที่อยู่ในBarการอนุญาตให้รหัสที่ได้นำมาใช้Barเพื่อนำมาปรับใช้Fooแทนโดยไม่ต้องเปลี่ยนอ้างอิงทั้งหมดไปthing.BarMemberด้วย thing.theBar.BarMemberและยังคงรักษาความสามารถในการอ่านและเขียนทั้งหมดBarของเขตข้อมูลเป็นกลุ่ม; ...
supercat

14

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


8
เราไม่จำเป็นต้องใช้ความหลากหลายเพื่อใช้ประโยชน์จากมรดก
rmeador

คุณมีการสืบทอดกี่ประเภทใน. NET?
John Saunders

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

นั่นเป็นเพราะพวกเขาทั้งหมดมาจาก System.Object มันเป็นความหลากหลายของ System.Object มากกว่าโครงสร้าง
John Saunders

Polymorphism อาจมีความหมายกับโครงสร้างที่ใช้เป็นพารามิเตอร์ประเภททั่วไป Polymorphism ทำงานร่วมกับโครงสร้างที่ใช้อินเทอร์เฟซ ปัญหาที่ใหญ่ที่สุดของอินเทอร์เฟซคือพวกเขาไม่สามารถแสดง byrefs ไปยังฟิลด์ struct มิฉะนั้นสิ่งที่ใหญ่ที่สุดที่ฉันคิดว่าจะเป็นประโยชน์พอ ๆ กับโครงสร้าง "สืบทอด" ก็คือการมีประเภท (โครงสร้างหรือคลาส) Fooที่มีเขตข้อมูลประเภทโครงสร้างที่Barสามารถถือว่าBarสมาชิกเป็นของตัวเองได้ดังนั้นPoint3dระดับทำได้เช่นแค็ปซูลPoint2d xyแต่หมายถึงXของเขตข้อมูลที่เป็นทั้งหรือxy.X X
supercat

8

นี่คือสิ่งที่เอกสารกล่าว:

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

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

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


4
นั่นไม่ใช่เหตุผลว่าทำไมถึงไม่มีมรดก
Dykam

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

กล่าวโดยย่อ: สามารถรองรับการถ่ายทอดทางพันธุกรรมโดยไม่มีความหลากหลาย มันไม่ ผมเชื่อว่ามันเป็นทางเลือกการออกแบบเพื่อช่วยให้คนเลือกclassมากกว่าstructเมื่อเหมาะสม
Blixt

3

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

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

หากเป็นไปได้จะมีข้อผิดพลาดกับตัวอย่างต่อไปนี้:

void DoSomething(A arg){};

...

B b;
DoSomething(b);

มีการจัดสรรพื้นที่สำหรับขนาด A ไม่ใช่สำหรับขนาด B


2
C ++ จัดการกรณีนี้ได้ดี IIRC อินสแตนซ์ของ B ถูกหั่นบาง ๆ ให้พอดีกับขนาดของ A ถ้าเป็นชนิดข้อมูลบริสุทธิ์ตามที่มีโครงสร้าง. NET จะไม่มีอะไรเลวร้ายเกิดขึ้น คุณประสบปัญหาเล็กน้อยกับเมธอดที่ส่งคืน A และคุณกำลังจัดเก็บค่าที่ส่งคืนใน B แต่ไม่ควรได้รับอนุญาต ในระยะสั้นนักออกแบบ. NET สามารถจัดการกับสิ่งนี้ได้หากต้องการ แต่ก็ไม่ได้ทำด้วยเหตุผลบางประการ
rmeador

1
สำหรับ DoSomething () ของคุณไม่น่าจะมีปัญหาเนื่องจาก (สมมติว่ามีความหมาย C ++) "b" จะถูก "แบ่งส่วน" เพื่อสร้างอินสแตนซ์ A ปัญหาอยู่ที่ <i> อาร์เรย์ </i> พิจารณาโครงสร้าง A และ B ที่คุณมีอยู่และวิธี <c> DoSomething (A [] arg) {arg [1] .property = 1;} </c> เนื่องจากอาร์เรย์ของประเภทค่าเก็บค่า "แบบอินไลน์" DoSomething (actual = new B [2] {}) จะทำให้ [0] .childproperty เป็นจริงไม่ใช่ [1] .property จริง นี้ไม่ดี.
jonp

2
@ จอห์น: ฉันไม่ได้ยืนยันว่ามันเป็นและฉันไม่คิดว่า @jonp ก็เช่นกัน เราเพียงแค่พูดถึงว่าปัญหานี้เก่าและได้รับการแก้ไขแล้วดังนั้นนักออกแบบ. NET จึงเลือกที่จะไม่สนับสนุนด้วยเหตุผลบางประการนอกเหนือจากความเป็นไปไม่ได้ทางเทคนิค
rmeador

ควรสังเกตว่าปัญหา "อาร์เรย์ของประเภทที่ได้รับ" ไม่ใช่เรื่องใหม่สำหรับ C ++ ดูparashift.com/c++-faq-lite/proper-inheritance.html#faq-21.4 (อาร์เรย์ใน C ++ นั้นชั่วร้าย! ;-)
jonp

@ จอห์น: วิธีแก้ปัญหา "อาร์เรย์ของประเภทที่ได้รับและประเภทฐานไม่ผสมกัน" คือตามปกติอย่าทำอย่างนั้น นั่นคือสาเหตุที่อาร์เรย์ใน C ++ ชั่วร้าย (อนุญาตให้หน่วยความจำเสียหายได้ง่ายขึ้น) และเหตุใด. NET จึงไม่สนับสนุนการสืบทอดด้วยชนิดค่า (คอมไพเลอร์และ JIT ทำให้แน่ใจว่าจะไม่เกิดขึ้น)
jonp

3

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

ในการพูดนั้น Cecil มีชื่อที่ตอกไว้อย่างถูกต้อง

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


3

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

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

แล้วการเพิ่มช่องล่ะ?

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

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

สิ่งนี้จะเขียนทับส่วนที่ไม่รู้จักบางส่วนของสแต็ก

อีกทางเลือกหนึ่งคือสำหรับรันไทม์เพื่อป้องกันสิ่งนี้โดยเขียนเฉพาะ sizeof (A) ไบต์ลงในตัวแปร A

จะเกิดอะไรขึ้นถ้า B แทนที่เมธอดใน A และอ้างถึงฟิลด์ Integer2 รันไทม์จะพ่น MemberAccessException หรือเมธอดจะเข้าถึงข้อมูลแบบสุ่มบนสแต็กแทน สิ่งเหล่านี้ไม่ได้รับอนุญาต

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


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

1
ประเภทค่าทั้งหมดใน. net จะถูกเติมเป็นศูนย์บน creastion โดยไม่คำนึงถึงประเภทหรือฟิลด์ที่มีอยู่ การเพิ่มตัวชี้ vtable ลงในโครงสร้างจะต้องใช้วิธีการกำหนดค่าเริ่มต้นด้วยค่าเริ่มต้นที่ไม่ใช่ศูนย์ คุณลักษณะดังกล่าวอาจมีประโยชน์สำหรับวัตถุประสงค์ที่หลากหลายและการนำสิ่งดังกล่าวไปใช้ในกรณีส่วนใหญ่อาจไม่ยากเกินไป แต่ไม่มีอะไรใกล้เคียงใน. net
supercat

@ user38001 "โครงสร้างถูกจัดสรรบนสแต็ก" - เว้นแต่ว่าจะเป็นฟิลด์อินสแตนซ์ซึ่งในกรณีนี้จะถูกจัดสรรบนฮีป
David Klempfner

2

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


คอมไพเลอร์รู้ว่ามีอะไรอยู่ การอ้างถึง C ++ นี่ไม่ใช่คำตอบ
Henk Holterman

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

ใช่การกล่าวถึง C ++ นั้นไม่ดีเพียงแค่ความคิดของฉัน แต่นอกเหนือจากคำถามหากต้องการข้อมูลรันไทม์แล้วทำไมโครงสร้างไม่ควรมี 'ส่วนหัวของวัตถุ' คอมไพเลอร์สามารถบดมันได้ตามต้องการ มันยังสามารถซ่อนส่วนหัวในโครงสร้าง [Structlayout]
Henk Holterman

เนื่องจากโครงสร้างเป็นชนิดค่าจึงไม่จำเป็นต้องใช้กับส่วนหัวของออบเจ็กต์เนื่องจากเวลาทำงานจะคัดลอกเนื้อหาเช่นเดียวกับชนิดค่าอื่น ๆ (ข้อ จำกัด ) มันไม่สมเหตุสมผลกับส่วนหัวเพราะนั่นคือคลาสประเภทอ้างอิงสำหรับ: P
Cecil มีชื่อ


0

IL เป็นภาษาที่ใช้สแต็กดังนั้นการเรียกเมธอดที่มีอาร์กิวเมนต์จะมีลักษณะดังนี้:

  1. ผลักอาร์กิวเมนต์ไปยังสแต็ก
  2. เรียกใช้วิธีการ

เมื่อเมธอดทำงานมันจะแสดงไบต์บางส่วนออกจากสแต็กเพื่อรับอาร์กิวเมนต์ มันรู้ว่าวิธีการหลายไบต์จะปรากฏออกเพราะอาร์กิวเมนต์เป็นทั้งตัวชี้ชนิดการอ้างอิง (เสมอ 4 ไบต์ 32 บิต) หรือจะเป็นชนิดของมูลค่าที่ขนาดเป็นที่รู้จักกันเสมอว่า

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

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


1
C ++ ได้แก้ไขแล้วอ่านคำตอบสำหรับปัญหาที่แท้จริงนี้: stackoverflow.com/questions/1222935/…
Dykam
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.