ตัวแปรใน C ++ เก็บประเภทของพวกเขาอย่างไร


42

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


8
คุณอ้างอิงถึงใครว่า " มัน " ใน " มันติดตามอย่างไร"? คอมไพเลอร์หรือซีพียูหรืออย่างอื่น / ชอบภาษาหรือโปรแกรม?
Erik Eidt


8
@ErikEidt IMO the OP เห็นได้ชัดว่าหมายถึง "ตัวแปรตัวเอง" โดย "มัน" แน่นอนคำตอบสองคำสำหรับคำถามคือ "ไม่"
alephzero

2
คำถามที่ดี! โดยเฉพาะอย่างยิ่งที่เกี่ยวข้องในวันนี้ให้ทุกภาษาแฟนซีที่เก็บประเภทของพวกเขา
เทรเวอร์บอยด์สมิ ธ

@alephzero นั่นเป็นคำถามที่ชัดเจน
Luaan

คำตอบ:


105

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

ตัวอย่างเช่นเราอาจจะมี struct หรือระดับและตัวแปรstruct Foo { int x; float y; }; Foo f {}การเข้าถึงฟิลด์auto result = f.y;จะรวบรวมได้อย่างไร? คอมไพเลอร์รู้ว่าfเป็นวัตถุประเภทFooและรู้โครงร่างของFoo-objects ขึ้นอยู่กับรายละเอียดเฉพาะแพลตฟอร์มซึ่งอาจรวบรวมเป็น“ นำตัวชี้ไปที่จุดเริ่มต้นfเพิ่ม 4 ไบต์จากนั้นโหลด 4 ไบต์และตีความข้อมูลนี้เป็นแบบลอย” ในชุดคำสั่งรหัสเครื่องจำนวนมาก (รวม x86-64 ) มีคำแนะนำโปรเซสเซอร์แตกต่างกันสำหรับการโหลดลอยหรือ ints

ตัวอย่างหนึ่งที่ระบบการพิมพ์ C ++ union Bar { int as_int; float as_float; }ไม่สามารถติดตามชนิดสำหรับเราเป็นสหภาพเช่น การรวมกันมีวัตถุได้หลายประเภท ถ้าเราเก็บวัตถุในสหภาพนี่คือประเภทที่ใช้งานของสหภาพ เราต้องพยายามดึงมันออกมาจากสหภาพ แต่อย่างอื่นจะมีพฤติกรรมที่ไม่ได้กำหนด ไม่ว่าเราจะ "รู้" ในขณะที่การเขียนโปรแกรมประเภทที่ใช้งานอยู่หรือเราสามารถสร้างสหภาพที่ติดแท็กที่เราเก็บแท็กประเภท (มักจะ enum) แยกต่างหาก นี่เป็นเทคนิคทั่วไปใน C แต่เนื่องจากเราต้องเก็บค่ายูเนี่ยนและแท็กประเภทให้ตรงกันจึงมีข้อผิดพลาดค่อนข้างง่าย void*ชี้คล้ายกับสหภาพ แต่สามารถเก็บวัตถุชี้ยกเว้นคำแนะนำการทำงาน
C ++ มีกลไกที่ดีกว่าสองข้อในการจัดการกับวัตถุประเภทที่ไม่รู้จัก: เราสามารถใช้เทคนิคเชิงวัตถุเพื่อทำการลบประเภท (โต้ตอบเฉพาะกับวัตถุด้วยวิธีเสมือนเพื่อที่เราไม่จำเป็นต้องรู้ประเภทจริง) หรือเราสามารถ ใช้std::variantชนิดของการรวมที่ปลอดภัยชนิด

มีกรณีหนึ่งที่ C ++ จัดเก็บชนิดของวัตถุคือ: หากคลาสของวัตถุนั้นมีวิธีการเสมือนใด ๆ (“ ชนิด polymorphic”, aka. interface) เป้าหมายของการเรียกใช้เมธอดเสมือนไม่เป็นที่รู้จักในเวลารวบรวมและได้รับการแก้ไขในเวลาทำงานตามประเภทไดนามิกของวัตถุ (“ การกระจายแบบไดนามิก”) คอมไพเลอร์ส่วนใหญ่จะใช้สิ่งนี้โดยการจัดเก็บตารางฟังก์ชันเสมือน (“ vtable”) ที่จุดเริ่มต้นของวัตถุ vtable ยังสามารถใช้เพื่อรับชนิดของวัตถุที่รันไทม์ จากนั้นเราสามารถวาดความแตกต่างระหว่างชนิดสแตติกที่รู้จักกันในเวลาคอมไพล์ของนิพจน์และชนิดของวัตถุแบบไดนามิกที่รันไทม์

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


3
ครอบคลุมมาก
Deduplicator

9
โปรดทราบว่าในการเข้าถึงชนิดของวัตถุ polymorphic นั้นคอมไพเลอร์ยังคงต้องรู้ว่าวัตถุนั้นเป็นของตระกูลมรดกเฉพาะ (เช่นมีการอ้างอิง / ตัวชี้ที่พิมพ์ไปยังวัตถุไม่ใช่void*)
Ruslan

5
+0 เพราะประโยคแรกไม่เป็นจริงทั้งสองย่อหน้าที่ถูกต้อง
Marcin

3
โดยทั่วไปสิ่งที่เก็บไว้ที่จุดเริ่มต้นของวัตถุ polymorphic เป็นตัวชี้ไปยังตารางวิธีเสมือนไม่ใช่ตารางตัวเอง
Peter Green

3
@ v.oddou ในย่อหน้าของฉันฉันไม่สนใจรายละเอียดบางอย่าง typeid(e)introspects eประเภทคงที่ของการแสดงออก หากประเภทสแตติกเป็นชนิด polymorphic นิพจน์จะได้รับการประเมินและจะดึงข้อมูลประเภทไดนามิกของวัตถุนั้น คุณไม่สามารถชี้ typeid ที่หน่วยความจำชนิดที่ไม่รู้จักและรับข้อมูลที่เป็นประโยชน์ เช่นประเภทของสหภาพอธิบายสหภาพไม่ใช่วัตถุในสหภาพ typeid ของ a void*เป็นเพียงตัวชี้โมฆะ และมันเป็นไปไม่ได้ที่จะเอ่ยvoid*ถึงเนื้อหาของมัน ใน C ++ ไม่มีการชกมวยเว้นแต่จะตั้งโปรแกรมไว้อย่างชัดเจน
amon

51

คำตอบอื่น ๆ อธิบายด้านเทคนิคได้ดี แต่ฉันต้องการเพิ่ม "วิธีคิดเกี่ยวกับรหัสเครื่อง" โดยทั่วไป

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

bool isEven(int i) { return i % 2 == 0; }

มันต้องใช้ int และคายบูลออกมา

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

คั้นน้ำผลไม้สีส้มอัตโนมัติ

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

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

ข้อแม้คือว่ามีบางกรณีของรหัสที่ไม่ดีที่คอมไพเลอร์จะผ่าน ตัวอย่างคือ:

  • ประเภทหล่อไม่ถูกต้อง: ปลดเปลื้องอย่างชัดเจนจะถือว่าเป็นที่ถูกต้องและเป็นที่โปรแกรมเมอร์เพื่อให้มั่นใจว่าเขาจะไม่ได้หล่อvoid*ไปorange*เมื่อมีแอปเปิ้ลในส่วนอื่น ๆ ของตัวชี้
  • ปัญหาการจัดการหน่วยความจำเช่นพอยน์เตอร์พอยน์เตอร์พอยซันห้อยหรือขอบเขตการใช้งาน คอมไพเลอร์ไม่สามารถค้นหาส่วนใหญ่ได้
  • ฉันแน่ใจว่ามีอย่างอื่นที่ฉันหายไป

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


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

@ Calchas ขอบคุณสำหรับความคิดเห็นของคุณ! ประโยคนี้เป็นการพูดเกินจริงอย่างแน่นอน ฉันอธิบายปัญหาเล็กน้อยที่เป็นไปได้จริง ๆ แล้วพวกเขาค่อนข้างเกี่ยวข้องกับคำถาม
Frax

5
คำอุปมาที่ยอดเยี่ยมสำหรับรหัสเครื่อง! คำอุปมาของคุณก็ถูกทำให้ดีขึ้น 10 เท่าโดยภาพเช่นกัน!
เทรเวอร์บอยด์สมิ ธ

2
"ฉันแน่ใจว่ามีอย่างอื่นที่ฉันหายไป" - แน่นอน! void*coerces ของ C คือfoo*การส่งเสริมการคำนวณทางคณิตศาสตร์ตามปกติunionพิมพ์ punning NULLและnullptrแม้กระทั่งการมีตัวชี้ที่ไม่ดีคือ UB เป็นต้น แต่ฉันไม่คิดว่าการจดรายชื่อของสิ่งเหล่านั้นทั้งหมดจะช่วยปรับปรุงคำตอบของคุณอย่างมีนัยสำคัญ มันเป็นมันเป็น
เควิน

@ เควินฉันไม่คิดว่าจะต้องเพิ่ม C ที่นี่เนื่องจากคำถามถูกติดแท็กเป็น C ++ เท่านั้น และใน C ++ void*จะไม่แปลงเป็น implicitly foo*และunionไม่รองรับประเภท punning (มี UB)
Ruslan

3

ตัวแปรมีคุณสมบัติพื้นฐานจำนวนหนึ่งในภาษาเช่น C:

  1. ชื่อ
  2. ประเภท
  3. ขอบเขต
  4. ตลอดชีวิต
  5. สถานที่
  6. ค่า

ในซอร์สโค้ดของคุณตำแหน่ง (5) เป็นแนวคิดและตำแหน่งนี้ถูกอ้างอิงโดยชื่อ (1) ดังนั้นการประกาศตัวแปรที่ใช้ในการสร้างที่ตั้งและพื้นที่สำหรับค่า (6) และในบรรทัดอื่น ๆ ของแหล่งที่มาเราอ้างถึงสถานที่นั้นและค่าที่มันถือโดยการตั้งชื่อตัวแปรในการแสดงออกบางอย่าง

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

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

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

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

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

นอกเหนือจากประเภทพื้นฐานดั้งเดิมเราต้องเข้ารหัสสิ่งต่าง ๆ ในโครงสร้างข้อมูลและใช้อัลกอริทึมเพื่อจัดการพวกเขาในแง่ของดั้งเดิม

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

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


1
ระวัง "เมื่อการแปลเสร็จสมบูรณ์ชื่อจะถูกลืม" ... การเชื่อมโยงจะทำผ่านชื่อ ("ไม่ได้กำหนดสัญลักษณ์ xy") และอาจเกิดขึ้นได้ในขณะใช้งานด้วยการเชื่อมโยงแบบไดนามิก ดูblog.fesnel.com/blog/2009/08/19/... ไม่มีสัญลักษณ์การแก้ปัญหาแม้จะถูกปล้น: คุณต้องการชื่อฟังก์ชั่น (และฉันถือว่าตัวแปรทั่วโลก) สำหรับการเชื่อมโยงแบบไดนามิก ดังนั้นจึงสามารถลืมชื่อของวัตถุภายในได้เท่านั้น โดยวิธีการที่รายการที่ดีของคุณสมบัติตัวแปร
ปีเตอร์ - Reinstate Monica

@ PeterA.Schneider คุณพูดถูกในภาพรวมของสิ่งที่ linkers และ loader ยังมีส่วนร่วมและใช้ชื่อของฟังก์ชั่น (ทั่วโลก) และตัวแปรที่มาจากซอร์สโค้ด
Erik Eidt

ภาวะแทรกซ้อนเพิ่มเติมคือคอมไพเลอร์บางคนตีความกฎที่ต่อมาตรฐานที่มีวัตถุประสงค์เพื่อให้คอมไพเลอร์ถือว่าสิ่งบางอย่างจะไม่เป็นนามแฝงช่วยให้พวกเขาเห็นว่าการดำเนินงานที่เกี่ยวข้องกับประเภทที่แตกต่างกัน unsequenced, แม้ในกรณีที่ไม่ได้เกี่ยวข้องกับการ aliasing ตามที่เขียนไว้ สิ่งที่ได้รับเช่นuseT1(&unionArray[i].member1); useT2(&unionArray[j].member2); useT1(&unionArray[i].member1);, เสียงดังกราวและ GCC มีแนวโน้มที่จะคิดว่าตัวชี้ไปยังunionArray[j].member2ไม่สามารถเข้าถึงแม้ว่าทั้งสองจะได้มาจากที่เดียวกันunionArray[i].member1 unionArray[]
supercat

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

2

ถ้าฉันกำหนดตัวแปรของชนิดที่แน่นอนมันติดตามชนิดของตัวแปรได้อย่างไร

มีสองขั้นตอนที่เกี่ยวข้องที่นี่:

  • รวบรวมเวลา

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

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

  • Runtime

เอาต์พุตของคอมไพเลอร์ - คอมไพล์เอ็กซีคิ้วท์ - คือภาษาเครื่อง, ซึ่งโหลดเข้าสู่ RAM โดยระบบปฏิบัติการของคุณ, และดำเนินการโดยตรงโดยซีพียูของคุณ ในภาษาเครื่องไม่มีแนวคิด "พิมพ์" เลย - มีเพียงคำสั่งที่ทำงานในบางตำแหน่งใน RAM คำสั่งไม่แน่นอนมีชนิดถาวรที่พวกเขาทำงานด้วย (เช่นอาจจะมีคำสั่งภาษาเครื่อง "เพิ่มทั้งสองจำนวนเต็ม 16 บิตที่เก็บไว้ในสถานที่ที่ RAM 0x100 และ 0x521") แต่ไม่มีข้อมูลที่ใดก็ได้ในระบบนั้น ไบต์ที่ตำแหน่งเหล่านั้นจริง ๆ แล้วจะแทนจำนวนเต็ม มีการป้องกันจากข้อผิดพลาดประเภทไม่เป็นที่ทุกคนที่นี่


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

@ PeterA.Schneider แน่นอนว่ามี NullPOINTERException แต่มีความแตกต่างที่ชัดเจนระหว่างการอ้างอิงและตัวชี้ในภาษาที่ฉันพูดถึง (เช่น Java, ruby, อาจ C #, Perl แม้ในระดับหนึ่ง) - การอ้างอิงไปด้วยกัน ด้วยระบบชนิดของพวกเขาการรวบรวมขยะการจัดการหน่วยความจำอัตโนมัติ ฯลฯ โดยทั่วไปไม่สามารถระบุตำแหน่งหน่วยความจำได้ (เช่นchar *ptr = 0x123ใน C) ฉันเชื่อว่าการใช้คำว่า "ตัวชี้" ของฉันควรจะชัดเจนในบริบทนี้ ถ้าไม่โปรดอย่าลังเลที่จะให้ฉันและฉันจะเพิ่มประโยคในคำตอบ
AnoE

ตัวชี้ "ไปพร้อมกับระบบประเภท" ใน C ++ เช่นกัน ;-) (ที่จริงแล้วจาวาคลาสสิกของ Java นั้นพิมพ์น้อยกว่า C ++) Garbage collection เป็นคุณสมบัติที่ C ++ ตัดสินใจที่จะไม่บังคับ แต่เป็นไปได้สำหรับการนำไปใช้งานและมันไม่มีส่วนเกี่ยวข้องกับคำที่เราใช้สำหรับพอยน์เตอร์
ปีเตอร์ - Reinstate Monica

ตกลง @ PeterA.Schneider ฉันไม่คิดว่าเราจะได้รับระดับที่นี่ ฉันลบย่อหน้าที่ฉันกล่าวถึงพอยน์เตอร์แล้ว แต่ไม่ได้ทำอะไรเลยเพื่อตอบคำถาม
AnoE

1

มีกรณีพิเศษที่สำคัญสองสามข้อที่ C ++ จัดเก็บชนิดที่รันไทม์

โซลูชันแบบคลาสสิกเป็นสหภาพแบบแยกส่วน:โครงสร้างข้อมูลที่มีหนึ่งในหลายประเภทของวัตถุรวมถึงเขตข้อมูลที่ระบุว่ามีประเภทใดในปัจจุบัน รุ่นเทมเพลตอยู่ในห้องสมุดมาตรฐาน c ++ std::variantเป็น โดยปกติแท็กจะเป็นenumแต่ถ้าคุณไม่ต้องการพื้นที่เก็บข้อมูลทั้งหมดสำหรับข้อมูลของคุณมันอาจเป็นบิตฟิลด์

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

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

แม้ว่ามาตรฐานภาษาจะไม่บอกคนที่ออกแบบคอมไพเลอร์ว่าพวกเขาจะต้องใช้งาน runtime อย่างไรภายใต้ประทุนนี่คือวิธีที่คุณคาดหวังdynamic_castและtypeofทำงาน


"มาตรฐานภาษาไม่ได้บอกผู้เขียนโค้ด" คุณน่าจะเน้นว่า "coders" ที่เป็นปัญหาคือคนที่เขียน gcc, clang, msvc และอื่น ๆ ไม่ใช่คนที่ใช้เหล่านั้นเพื่อรวบรวม C ++ ของพวกเขา
Caleth

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