ผู้ประกาศชนิดข้อมูลเช่น "int" และ "char" ถูกเก็บไว้ใน RAM เมื่อโปรแกรม C ทำงานหรือไม่


74

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

พิจารณารหัสต่อไปนี้:

char a = 'A';
int x = 4;

ฉันอ่านว่า A และ 4 ถูกเก็บไว้ในที่อยู่ RAM ที่นี่ แต่สิ่งที่เกี่ยวaและx? ส่วนใหญ่สับสนวิธีการที่ไม่ดำเนินการที่รู้ว่าaเป็นถ่านและxเป็น int? ฉันหมายถึงเป็นintและcharพูดถึงบางแห่งใน RAM?

สมมติว่าค่าถูกเก็บไว้ที่ใดที่หนึ่งใน RAM เช่น 10011001 ถ้าฉันเป็นโปรแกรมที่รันโค้ดฉันจะรู้ได้อย่างไรว่า 10011001 นี้เป็นcharหรือintไม่?

สิ่งที่ผมไม่เข้าใจคือวิธีการใช้คอมพิวเตอร์รู้เมื่อมันอ่านค่าของตัวแปรจากที่อยู่เช่น 10001 ไม่ว่าจะเป็นหรือint ลองนึกภาพผมคลิกที่โปรแกรมที่เรียกว่าchar anyprog.exeรหัสทันทีเริ่มดำเนินการ ไฟล์ที่ปฏิบัติการได้นี้รวมถึงข้อมูลว่าตัวแปรที่เก็บไว้เป็นประเภทintหรือcharไม่?


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

4
มันไม่ได้ เนื่องจากถือว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่จึงใช้ทุกสิ่งที่พบที่อยู่หน่วยความจำที่คุณให้ไว้และเขียนลงใน stdout หากสิ่งใดก็ตามที่เขียนนั้นตรงกับตัวอักษรที่อ่านได้มันจะปรากฏขึ้นบนคอนโซลของใครบางคนในฐานะตัวอักษรที่อ่านได้ หากไม่สอดคล้องกันก็จะปรากฏเป็นพูดพล่อยๆหรืออาจเป็นตัวอักษรที่อ่านได้แบบสุ่ม
Robert Harvey

22
@ user16307 คำตอบสั้น ๆ ก็คือในภาษาที่พิมพ์แบบคงที่ทุกครั้งที่คุณพิมพ์อักขระ char คอมไพเลอร์จะสร้างโค้ดที่แตกต่างจากที่ใช้ในการพิมพ์ int ที่รันไทม์ไม่มีความรู้ใด ๆ ที่xเป็นตัวอักษรอีกต่อไปแต่เป็นรหัสการพิมพ์ตัวอักษรที่ถูกเรียกใช้เพราะนั่นคือสิ่งที่คอมไพเลอร์เลือก
Ixrec

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

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

คำตอบ:


122

หากต้องการตอบคำถามที่คุณโพสต์ไว้ในความคิดเห็นหลายข้อ (ซึ่งฉันคิดว่าคุณควรแก้ไขในโพสต์ของคุณ):

สิ่งที่ฉันไม่เข้าใจคือคอมพิวเตอร์รู้ได้อย่างไรว่ามันช่วยให้เมื่ออ่านค่าของตัวแปรจากและที่อยู่เช่น 10001 ถ้าเป็น int หรืออักขระ ลองนึกภาพฉันคลิกที่โปรแกรมที่ชื่อว่า anyprog.exe รหัสทันทีเริ่มดำเนินการ ไฟล์ exe นี้มีข้อมูลเกี่ยวกับว่าตัวแปรถูกเก็บไว้ในหรือไม่?

ดังนั้นให้ใส่รหัสลงไป สมมติว่าคุณเขียน:

int x = 4;

และสมมติว่ามันถูกเก็บไว้ใน RAM:

0x00010004: 0x00000004

ส่วนแรกเป็นที่อยู่ส่วนที่สองเป็นค่า เมื่อโปรแกรมของคุณ (ที่รันเป็นรหัสเครื่อง) วิ่งทั้งหมดก็เห็นที่เป็นค่า0x00010004 0x000000004ไม่ทราบชนิดของข้อมูลนี้และไม่ทราบว่าควรใช้อย่างไร

ดังนั้นโปรแกรมของคุณจะหาสิ่งที่ถูกต้องอย่างไร พิจารณารหัสนี้:

int x = 4;
x = x + 5;

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

อีกวิธีที่จะคิดว่ามันเป็นเช่นนี้ ฉันให้ข้อมูลนี้กับคุณ:

0x00000004: 0x12345678

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

บางทีความสับสนบางอย่างอาจทำให้ข้อมูลกลับมา ถ้าเรามี:

char A = 'a';

คอมพิวเตอร์รู้ได้อย่างไรว่าจะแสดงaในคอนโซล? มีขั้นตอนมากมายในการทำเช่นนั้น วิธีแรกคือไปที่Aตำแหน่ง s ในหน่วยความจำและอ่าน:

0x00000004: 0x00000061

ค่าฐานสิบหกสำหรับaใน ASCII คือ 0x61 ดังนั้นด้านบนอาจเป็นสิ่งที่คุณเห็นในหน่วยความจำ ดังนั้นตอนนี้รหัสเครื่องของเรารู้ค่าจำนวนเต็ม จะทราบได้อย่างไรว่าการเปลี่ยนค่าจำนวนเต็มเป็นอักขระเพื่อแสดงมัน เพียงแค่ใส่คอมไพเลอร์ตรวจสอบให้แน่ใจว่าได้ใส่ขั้นตอนที่จำเป็นทั้งหมดเพื่อทำการเปลี่ยนแปลงนั้น แต่คอมพิวเตอร์ของคุณเอง (หรือโปรแกรม / exe) ไม่ทราบว่าเป็นข้อมูลประเภทใด ค่า 32 บิตนั้นอาจเป็นอะไรก็ได้ - int,, charครึ่งหนึ่งของ a double, ตัวชี้, ส่วนหนึ่งของอาร์เรย์, ส่วนของ a string, ส่วนหนึ่งของคำสั่ง ฯลฯ


นี่เป็นการโต้ตอบสั้น ๆ ที่โปรแกรมของคุณ (exe) อาจมีกับคอมพิวเตอร์ / ระบบปฏิบัติการ

โปรแกรม: ฉันต้องการเริ่มต้น ฉันต้องการหน่วยความจำ 20 MB

ระบบปฏิบัติการ: ค้นหาหน่วยความจำฟรี 20 MB ที่ไม่ได้ใช้งานและส่งมอบให้

(หมายเหตุสำคัญคือสิ่งนี้สามารถคืนหน่วยความจำ 20 MB ใด ๆได้โดยไม่ต้องต่อเนื่องกัน ณ จุดนี้โปรแกรมสามารถทำงานภายในหน่วยความจำที่มีโดยไม่ต้องพูดคุยกับระบบปฏิบัติการ)

โปรแกรม: xฉันจะคิดว่าจุดแรกในความทรงจำเป็นตัวแปรจำนวนเต็ม

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

โปรแกรม: ตอนนี้ฉันจะเขียน2ถึงสี่ไบต์แรกที่ฉันคิดว่าxอยู่ที่

โปรแกรม: ฉันต้องการเพิ่ม 5 xถึง

  • อ่านค่า X ลงในการลงทะเบียนชั่วคราว

  • เพิ่ม 5 ไปยังการลงทะเบียนชั่วคราว

  • xร้านคุ้มค่าของการลงทะเบียนชั่วคราวกลับเข้ามาในไบต์แรกซึ่งจะถือว่ายังคงเป็น

โปรแกรม: yฉันจะถือว่าไบต์ใช้ได้ต่อไปเป็นตัวแปรถ่าน

โปรแกรม: ฉันจะเขียนให้กับตัวแปรay

  • ไลบรารีถูกใช้เพื่อค้นหาค่าไบต์สำหรับ a

  • yไบต์ถูกเขียนไปยังที่อยู่โปรแกรมจะสมมติว่าเป็น

โปรแกรม: ฉันต้องการที่จะแสดงเนื้อหาของ y

  • อ่านค่าในจุดหน่วยความจำที่สอง

  • ใช้ไลบรารีเพื่อแปลงจากไบต์เป็นอักขระ

  • ใช้ไลบรารีกราฟิกเพื่อเปลี่ยนหน้าจอคอนโซล (ตั้งค่าพิกเซลจากขาวดำเลื่อนหนึ่งบรรทัด ฯลฯ )

(และจากที่นี่)

สิ่งที่คุณอาจจะได้รับการแขวนบนเป็น - สิ่งที่เกิดขึ้นเมื่อจุดแรกในความทรงจำไม่มีอีกต่อไปx? หรือครั้งที่สองจะไม่มีอีกต่อไปy? เกิดอะไรขึ้นเมื่อมีคนอ่านxเป็นcharหรือyเป็นตัวชี้? ในระยะสั้นสิ่งเลวร้ายเกิดขึ้น สิ่งเหล่านี้บางอย่างมีพฤติกรรมที่ชัดเจนและบางอย่างมีพฤติกรรมที่ไม่ได้กำหนด พฤติกรรมที่ไม่ได้กำหนดเป็นสิ่งที่แน่นอน - ทุกสิ่งสามารถเกิดขึ้นได้จากอะไรไปจนถึงการหยุดทำงานของโปรแกรมหรือระบบปฏิบัติการ แม้แต่พฤติกรรมที่กำหนดไว้อย่างดีก็อาจเป็นอันตรายได้ หากฉันสามารถเปลี่ยนxเป็นตัวชี้ไปยังโปรแกรมของฉันและทำให้โปรแกรมของคุณใช้เป็นตัวชี้ได้ฉันสามารถให้โปรแกรมของคุณเริ่มต้นใช้งานโปรแกรมของฉัน - ซึ่งเป็นสิ่งที่แฮ็คเกอร์ทำ คอมไพเลอร์อยู่ที่นั่นเพื่อช่วยให้แน่ใจว่าเราไม่ได้ใช้int xเป็นstringและสิ่งต่าง ๆ ในธรรมชาตินั้น รหัสเครื่องนั้นไม่ได้รับรู้ถึงประเภทและจะทำตามคำแนะนำที่บอกให้ทำเท่านั้น นอกจากนี้ยังมีข้อมูลจำนวนมากที่ค้นพบในขณะใช้งาน: หน่วยความจำไบต์ใดบ้างที่โปรแกรมอนุญาตให้ใช้? ไม่xเริ่มต้นที่ไบต์แรกหรือ 12?

แต่คุณสามารถจินตนาการได้ว่าการเขียนโปรแกรมแบบนี้น่ากลัวแค่ไหน (และคุณสามารถทำได้ในภาษาแอสเซมบลี) คุณเริ่มต้นด้วย 'ประกาศ' ตัวแปรของคุณ - คุณบอกตัวเองว่าไบต์ 1 xไบต์ 2 yและตามที่คุณเขียนแต่ละบรรทัดของรหัสโหลดและจัดเก็บลงทะเบียนคุณ (เป็นมนุษย์) ต้องจำไว้เป็นที่หนึ่งxและที่ สิ่งหนึ่งคือyเพราะระบบไม่มีความคิด และคุณ (ในฐานะมนุษย์) ต้องจำประเภทxและyเป็นเพราะอีกครั้ง - ระบบไม่มีความคิด


คำอธิบายที่น่าอัศจรรย์ เฉพาะส่วนที่คุณเขียน "มันจะเปลี่ยนค่าจำนวนเต็มเป็นอักขระเพื่อแสดงได้อย่างไรเพียงแค่ใส่คอมไพเลอร์ตรวจสอบให้แน่ใจว่าได้ใส่ขั้นตอนที่จำเป็นทั้งหมดเพื่อทำการเปลี่ยนแปลงนั้น" ยังคงมีหมอกสำหรับฉัน สมมติว่า CPU ดึงข้อมูล 0x00000061 จากการลงทะเบียน RAM จากจุดนี้คุณกำลังบอกว่ามีคำแนะนำอื่น ๆ (ในไฟล์ exe) ซึ่งทำให้การเปลี่ยนแปลงนั้นเป็นสิ่งที่เราเห็นบนหน้าจอ?
user16307

2
@ user16307 ใช่มีคำแนะนำเพิ่มเติม แต่ละบรรทัดของรหัสที่คุณเขียนสามารถเปลี่ยนเป็นคำแนะนำมากมาย มีคำแนะนำในการหาว่าตัวอักษรที่ใช้คืออะไรมีคำแนะนำในการปรับเปลี่ยนพิกเซลและสีที่เปลี่ยนไปและอื่น ๆ นอกจากนี้ยังมีรหัสที่คุณไม่เห็น ตัวอย่างเช่นการใช้ std :: cout หมายความว่าคุณกำลังใช้ห้องสมุด รหัสของคุณที่จะเขียนไปยังคอนโซลอาจเป็นเพียงหนึ่งบรรทัด แต่ฟังก์ชั่นที่คุณโทรจะมีจำนวนบรรทัดมากขึ้นและแต่ละบรรทัดสามารถเปลี่ยนเป็นคำแนะนำของเครื่องได้มากมาย
Shaz

8
@ user16307 Otherwise how can console or text file outputs a character instead of int เนื่องจากมีลำดับขั้นตอนที่แตกต่างกันสำหรับการแสดงผลเนื้อหาของตำแหน่งหน่วยความจำเป็นจำนวนเต็มหรือเป็นตัวอักษรและตัวเลข คอมไพเลอร์ทราบเกี่ยวกับประเภทตัวแปรและเลือกลำดับที่เหมาะสมของคำสั่งในเวลารวบรวมและบันทึกใน EXE
Charles E. Grant

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

6
@ user16307 พยายามอย่ากังวลเกี่ยวกับ C ++ และ C # สิ่งที่คนเหล่านี้พูดคือวิธีที่เหนือความเข้าใจปัจจุบันของคุณเกี่ยวกับการทำงานของคอมพิวเตอร์และคอมไพเลอร์ สำหรับวัตถุประสงค์ของสิ่งที่คุณพยายามที่จะเข้าใจฮาร์ดแวร์ไม่ทราบอะไรเกี่ยวกับประเภท char หรือ int หรืออะไรก็ตาม เมื่อคุณบอกคอมไพเลอร์ว่าตัวแปรบางตัวเป็น int มันจะสร้างรหัสที่สามารถใช้งานได้เพื่อจัดการกับตำแหน่งหน่วยความจำ AS ถ้ามันเป็น int ตำแหน่งหน่วยความจำไม่มีข้อมูลเกี่ยวกับประเภท; มันเป็นเพียงที่โปรแกรมของคุณตัดสินใจที่จะถือเป็น int ลืมทุกอย่างที่คุณเคยได้ยินเกี่ยวกับข้อมูลประเภทรันไทม์
Andres F.

43

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

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

มีมีเหตุผลที่จะรักษาประเภทที่รันไทม์:

  • การพิมพ์แบบไดนามิก: ในการพิมพ์แบบไดนามิกการตรวจสอบชนิดเกิดขึ้นที่รันไทม์ดังนั้นแน่นอนว่าต้องทราบประเภทขณะใช้งานจริง แต่ C ไม่พิมพ์แบบไดนามิกดังนั้นประเภทสามารถลบได้อย่างปลอดภัย (โปรดทราบว่านี่เป็นสถานการณ์ที่แตกต่างกันมากแม้ว่า Dynamic Types และ Static Types จะไม่เหมือนกันจริง ๆ และในภาษาแบบผสมคุณสามารถลบประเภทคงที่และเก็บเฉพาะประเภทไดนามิกเท่านั้น)
  • Dynamic Polymorphism: หากคุณเรียกใช้รหัสที่แตกต่างกันตามชนิดของรันไทม์คุณจะต้องรักษาชนิดของรันไทม์เอาไว้ C ไม่มีไดนามิกหลายรูปแบบ (ไม่มีความแตกต่างใด ๆ จริง ๆ ยกเว้นในกรณีที่มีการเข้ารหัสพิเศษบางกรณีเช่นตัว+ดำเนินการ) ดังนั้นจึงไม่จำเป็นต้องใช้ชนิดรันไทม์ด้วยเหตุผลนั้น อย่างไรก็ตามอีกครั้งประเภทรันไทม์เป็นสิ่งที่แตกต่างจากประเภทคงที่ต่อไปเช่นใน Java คุณสามารถลบประเภทคงที่ในทางทฤษฎีและยังคงเก็บประเภทรันไทม์สำหรับ polymorphism โปรดทราบว่าหากคุณกระจายอำนาจและระบุรหัสการค้นหาประเภทและวางไว้ในวัตถุ (หรือคลาส) คุณก็ไม่จำเป็นต้องใช้ runtime-type เช่น C ++ vtables
  • Runtime Reflection: หากคุณอนุญาตให้โปรแกรมสะท้อนชนิดของมันที่รันไทม์คุณต้องเก็บประเภทไว้ที่ runtime คุณสามารถเห็นสิ่งนี้ได้ง่าย ๆ ด้วย Java ซึ่งเก็บลำดับประเภทแรกไว้ที่รันไทม์ แต่จะลบอาร์กิวเมนต์ประเภทเป็นประเภททั่วไปในเวลารวบรวมดังนั้นคุณสามารถสะท้อนเฉพาะกับตัวสร้างประเภท ("raw type") แต่ไม่ใช่อาร์กิวเมนต์ชนิด อีกครั้ง C ไม่มีการสะท้อนกลับรันไทม์ดังนั้นจึงไม่จำเป็นต้องคงชนิดไว้ที่รันไทม์

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

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

ประเภทการรักษาที่รันไทม์จำเป็นเฉพาะเมื่อคุณต้องการทำบางอย่างกับประเภทที่รันไทม์

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


3
No! ทำไม? ข้อมูลนั้นจำเป็นสำหรับอะไร? คอมไพเลอร์เอาท์พุทรหัสสำหรับการอ่านcharลงในไบนารีที่รวบรวม มันไม่ออกรหัสสำหรับการintก็ไม่ได้ส่งออกรหัสสำหรับbyteมันไม่ได้ส่งออกรหัสสำหรับตัวชี้มันก็ออกผลลัพธ์เพียงcharรหัสสำหรับ ไม่มีการตัดสินใจรันไทม์ตามประเภท คุณไม่จำเป็นต้องพิมพ์ มันไม่เกี่ยวข้องอย่างสมบูรณ์และอย่างเต็มที่ การตัดสินใจที่เกี่ยวข้องทั้งหมดได้ทำไปแล้วในเวลารวบรวม
Jörg W Mittag

2
ไม่มี คอมไพเลอร์เพียงแค่ใส่รหัสสำหรับการพิมพ์อักขระในไบนารี ระยะเวลา คอมไพเลอร์รู้ว่าที่ที่อยู่หน่วยความจำนั้นมีถ่านดังนั้นจึงใส่รหัสสำหรับการพิมพ์อักขระในไบนารี หากค่าที่อยู่ในหน่วยความจำนั้นด้วยเหตุผลแปลก ๆ บางอย่างเกิดขึ้นไม่ใช่ตัวละครแล้วล่ะก็นรกทั้งหมดก็แตกสลาย โดยทั่วไปแล้วการรักษาความปลอดภัยทั้งระบบจะทำงานอย่างไร
Jörg W Mittag

2
ลองคิดดูสิถ้าซีพียูรู้เกี่ยวกับประเภทข้อมูลของโปรแกรมทุกคนบนโลกจะต้องซื้อซีพียูใหม่ทุกครั้งที่มีคนคิดค้นรูปแบบใหม่ public class JoergsAwesomeNewType {};ดู? ฉันเพิ่งคิดค้นรูปแบบใหม่! คุณต้องซื้อ CPU ใหม่!
Jörg W Mittag

9
ไม่มันไม่ใช่ คอมไพเลอร์รู้รหัสที่ต้องใส่ในไบนารี ไม่มีจุดในการเก็บข้อมูลนี้ หากคุณกำลังพิมพ์ int คอมไพเลอร์จะวางรหัสสำหรับการพิมพ์ int หากคุณกำลังพิมพ์อักขระตัวแปลภาษาคอมไพเลอร์จะใส่รหัสสำหรับการพิมพ์ตัวอักษร ระยะเวลา แต่มันเป็นเพียงรูปแบบเล็กน้อย รหัสสำหรับการพิมพ์ตัว char จะแปลความหมายของบิตแพทเทิร์นในทางใดทางหนึ่งโค้ดสำหรับการพิมพ์ int จะแปลความหมายของบิตในวิธีที่ต่างกัน แต่ไม่มีวิธีที่จะแยกความแตกต่างของบิตแพทเทิร์น เป็นถ่านมันเป็นสตริงของบิต
Jörg W Mittag

2
@ user16307: "ไฟล์ exe ไม่มีข้อมูลเกี่ยวกับที่อยู่ประเภทใดของข้อมูล" อาจจะ. หากคุณรวบรวมข้อมูลการดีบักข้อมูลการดีบักจะรวมข้อมูลเกี่ยวกับชื่อตัวแปรที่อยู่และประเภท และบางครั้งข้อมูลการดีบักจะถูกเก็บไว้ในไฟล์. exe (เป็นสตรีมไบนารี่) แต่มันไม่ได้เป็นส่วนหนึ่งของรหัสที่ปฏิบัติการได้และมันไม่ได้ถูกใช้โดยแอพพลิเคชั่นเองโดยเฉพาะบั๊กเท่านั้น
Ben Voigt

12

คอมพิวเตอร์ไม่ "รู้" ว่าที่อยู่คืออะไร แต่รู้ว่าอะไรคือสิ่งที่อบเข้าสู่คำแนะนำของโปรแกรมของคุณ

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

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

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


คุณจะอธิบายได้อย่างไรว่า CPU ค้นหา 0x00000061 ที่รีจิสเตอร์และดึงข้อมูลมา; และจินตนาการว่าโปรแกรมคอนโซลควรให้ผลลัพธ์นี้เป็นอักขระที่ไม่ได้ใช้งานได้ คุณหมายถึงว่าในไฟล์ exe นั้นมีบางรหัสการเรียนการสอนที่รู้ที่อยู่ของ 0x00000061 เป็นตัวอักษรและแปลงเป็นอักขระโดยใช้ตาราง ASCII?
user16307

7
โปรดทราบว่า "ทุกอย่างขัดข้อง" เป็นกรณีที่ดีที่สุด "สิ่งประหลาดเกิดขึ้น" เป็นสถานการณ์ที่ดีที่สุดอันดับที่สอง "สิ่งต่าง ๆ ที่เกิดขึ้นอย่างแปลกประหลาด" ยิ่งแย่ลงและที่เลวร้ายที่สุดก็คือ "สิ่งต่าง ๆ เกิดขึ้นหลังของคุณ หรือที่รู้จักในเรื่องการรักษาความปลอดภัย
Jörg W Mittag

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

1
@ JörgWMittag: แน่นอน ฉันคิดเกี่ยวกับการพูดถึง buffer overflow เป็นตัวอย่าง แต่ตัดสินใจว่ามันจะทำให้สับสนมากขึ้น
whatsisname

@ user16307: สิ่งที่แสดงข้อมูลไปยังหน้าจอคือโปรแกรม สำหรับยูนิกซ์ดั้งเดิมนั้นเป็นเทอร์มินัล (ชิ้นส่วนของซอฟต์แวร์ที่จำลองเทอร์มินัลอนุกรม VT VT100 - อุปกรณ์ฮาร์ดแวร์ที่มีหน้าจอและคีย์บอร์ดที่แสดงสิ่งที่เข้าสู่โมเด็มของมันไปยังจอภาพและส่งสิ่งที่พิมพ์บนคีย์บอร์ด ใน DOS มันคือ DOS (จริง ๆ แล้วคือโหมดข้อความของการ์ด VGA ของคุณ แต่ให้เพิกเฉยได้) และใน Windows มันเป็น command.com โปรแกรมของคุณไม่ทราบว่ามันกำลังพิมพ์สตริงจริง ๆ มันแค่พิมพ์ลำดับของไบต์ (ตัวเลข)
slebetman

8

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

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


สิ่งที่ฉันไม่เข้าใจคือคอมพิวเตอร์รู้ได้อย่างไรว่ามันช่วยให้เมื่ออ่านค่าของตัวแปรจากและที่อยู่เช่น 10001 ถ้าเป็น int หรืออักขระ ลองนึกภาพฉันคลิกที่โปรแกรมที่ชื่อว่า anyprog.exe รหัสทันทีเริ่มดำเนินการ ไฟล์ exe นี้มีข้อมูลเกี่ยวกับว่าตัวแปรถูกเก็บไว้ในหรือไม่? -
user16307

@ user16307 ไม่ไม่มีข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่เป็น int หรือ char ฉันจะเพิ่มตัวอย่างบางสิ่งภายหลังโดยสมมติว่าไม่มีใครชนะฉันเลย
8bittree

1
@ user16307: ไฟล์ exe มีข้อมูลนั้นทางอ้อม ตัวประมวลผลที่ดำเนินการโปรแกรมไม่สนใจประเภทที่ใช้เมื่อเขียนโปรแกรม แต่ส่วนใหญ่สามารถสรุปได้จากคำแนะนำที่ใช้เพื่อเข้าถึงหน่วยความจำตำแหน่งต่าง ๆ
Bart van Ingen Schenau

@ user16307 มีข้อมูลเพิ่มเติมเล็กน้อย ไฟล์ exe รู้ว่าจำนวนเต็มคือ 4 ไบต์ดังนั้นเมื่อคุณเขียน "int a" คอมไพเลอร์จอง 4 ไบต์สำหรับตัวแปรและสามารถคำนวณที่อยู่ของและตัวแปรอื่น ๆ หลังจากนั้น
Esben Skov Pedersen

1
@ user16307 ไม่มีความแตกต่างในทางปฏิบัติ (ด้านข้างประเภทขนาด) ความแตกต่างระหว่างint a = 65และchar b = 'A'เมื่อมีการรวบรวมรหัส

6

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

ตัวอย่างที่เป็นรูปธรรมบางอย่างอาจช่วยได้ รหัสเครื่องด้านล่างถูกสร้างโดยใช้ gcc 4.1.2 บนระบบ x86_64 ที่ใช้ SuSE Linux Enterprise Server (SLES) 10

สมมติว่าซอร์สโค้ดต่อไปนี้:

int main( void )
{
  int x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

นี่คือเนื้อของรหัสแอสเซมบลีที่สร้างขึ้นซึ่งสอดคล้องกับแหล่งที่มาด้านบน (โดยใช้gcc -S) โดยที่ความคิดเห็นที่เพิ่มโดยฉัน:

main:
.LFB2:
        pushq   %rbp               ;; save the current frame pointer value
.LCFI0:
        movq    %rsp, %rbp         ;; make the current stack pointer value the new frame pointer value
.LCFI1:                            
        movl    $1, -12(%rbp)      ;; x = 1
        movl    $2, -8(%rbp)       ;; y = 2
        movl    -8(%rbp), %eax     ;; copy the value of y to the eax register
        addl    -12(%rbp), %eax    ;; add the value of x to the eax register
        movl    %eax, -4(%rbp)     ;; copy the value in eax to z
        movl    $0, %eax           ;; eax gets the return value of the function
        leave                      ;; exit and restore the stack
        ret

มีบางสิ่งเพิ่มเติมที่ตามretมา แต่ไม่เกี่ยวข้องกับการสนทนา

%eaxเป็นการลงทะเบียนข้อมูลทั่วไป 32- บิต %rspคือการลงทะเบียนแบบ 64 บิตที่สงวนไว้สำหรับการบันทึกตัวชี้สแต็กซึ่งมีที่อยู่ของสิ่งสุดท้ายที่ผลักลงบนสแต็ก %rbpเป็น 64 บิตลงทะเบียนสงวนไว้สำหรับการประหยัดชี้กรอบที่มีอยู่ของปัจจุบันกองกรอบ กรอบสแต็กถูกสร้างขึ้นบนสแต็กเมื่อคุณเข้าสู่ฟังก์ชั่นและจะสงวนพื้นที่สำหรับอาร์กิวเมนต์ของฟังก์ชันและตัวแปรในเครื่อง อาร์กิวเมนต์และตัวแปรเข้าถึงได้โดยใช้ offsets จากตัวชี้เฟรม ในกรณีนี้หน่วยความจำสำหรับตัวแปรxคือ 12 ไบต์ "ด้านล่าง" %rbpที่อยู่ที่เก็บไว้ใน

ในรหัสข้างต้นเราคัดลอกค่าจำนวนเต็มของx(1, เก็บไว้ที่-12(%rbp)) ไปยังการลงทะเบียน%eaxโดยใช้movlคำสั่งซึ่งใช้ในการคัดลอกคำ 32- บิตจากที่หนึ่งไปอีกที่หนึ่ง จากนั้นเราจะเรียกaddlซึ่งจะเพิ่มค่าจำนวนเต็มของy(เก็บไว้ที่-8(%rbp)) %eaxค่าที่มีอยู่แล้วใน จากนั้นเราจะบันทึกผลการซึ่งเป็น -4(%rbp)z

ทีนี้ลองเปลี่ยนมันขึ้นมาแล้วเราก็จัดการกับdoubleค่าแทนที่จะเป็นintค่า:

int main( void )
{
  double x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

การทำงานgcc -Sอีกครั้งทำให้เรา:

main:
.LFB2:
        pushq   %rbp                              
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        movabsq $4607182418800017408, %rax ;; copy literal 64-bit floating-point representation of 1.00 to rax
        movq    %rax, -24(%rbp)            ;; save rax to x
        movabsq $4611686018427387904, %rax ;; copy literal 64-bit floating-point representation of 2.00 to rax
        movq    %rax, -16(%rbp)            ;; save rax to y
        movsd   -24(%rbp), %xmm0           ;; copy value of x to xmm0 register
        addsd   -16(%rbp), %xmm0           ;; add value of y to xmm0 register
        movsd   %xmm0, -8(%rbp)            ;; save result to z
        movl    $0, %eax                   ;; eax gets return value of function
        leave                              ;; exit and restore the stack
        ret

ความแตกต่างมากมาย แทนที่จะเป็นmovlและaddlเราใช้movsdและaddsd(กำหนดและเพิ่มความแม่นยำสองเท่าของลอย) แทนการจัดเก็บค่าระหว่างกาลที่เราใช้%eax%xmm0

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


4

ในอดีต C ได้รับการยกย่องว่าเป็นหน่วยความจำซึ่งประกอบด้วยกลุ่มของช่องหมายเลขที่มีประเภทunsigned char(เรียกอีกอย่างว่า "ไบต์" แม้ว่าจะไม่จำเป็นต้องเป็น 8 บิตเสมอไป) รหัสใด ๆ ที่ใช้สิ่งใดที่เก็บไว้ในหน่วยความจำจะต้องรู้ว่าช่องไหนหรือช่องที่เก็บข้อมูลไว้และรู้ว่าควรทำอะไรกับข้อมูลที่นั่น [เช่น "ตีความสี่ไบต์เริ่มต้นที่ที่อยู่ 123: 456 เป็น 32 บิต ค่าจุดลอยตัว "หรือ" เก็บ 16 บิตต่ำสุดของปริมาณที่คำนวณล่าสุดลงในสองไบต์เริ่มต้นที่ที่อยู่ 345: 678] หน่วยความจำตัวเองจะไม่ทราบหรือดูแลสิ่งที่ค่าที่เก็บไว้ในช่องหน่วยความจำ "หมายถึง" ถ้า รหัสพยายามที่จะเขียนหน่วยความจำโดยใช้หนึ่งประเภทและอ่านเป็นอีกรูปแบบบิตที่เก็บไว้โดยการเขียนจะถูกตีความตามกฎของประเภทที่สองด้วยผลกระทบใด ๆ ที่อาจเกิดขึ้น

ตัวอย่างเช่นหากรหัสถูกจัดเก็บไว้0x12345678ที่ 32- บิตunsigned intแล้วพยายามอ่านunsigned intค่า16- บิตติดต่อกันสองค่าจากที่อยู่และที่อยู่ด้านบนแล้วขึ้นอยู่กับว่าครึ่งหนึ่งของที่unsigned intถูกเก็บไว้ที่ไหนรหัสอาจอ่านค่า 0x1234 และ 0x5678 หรือ 0x5678 และ 0x1234

C99 มาตรฐาน แต่ไม่ต้องว่าหน่วยความจำทำงานเป็นพวงของช่องเลขที่รู้อะไรเกี่ยวกับสิ่งที่รูปแบบบิตของพวกเขาเป็นตัวแทน คอมไพเลอร์ได้รับอนุญาตให้ทำงานราวกับว่าสล็อตหน่วยความจำทราบประเภทของข้อมูลที่เก็บไว้ในพวกเขาและจะอนุญาตเฉพาะข้อมูลที่เขียนโดยใช้ประเภทอื่นนอกเหนือจากunsigned charที่จะอ่านโดยใช้ประเภทใดประเภทunsigned charเดียวกันหรือเป็นชนิดเดียวกับที่เขียน กับ; คอมไพเลอร์ยังได้รับอนุญาตให้ทำงานเหมือนช่องเสียบหน่วยความจำมีอำนาจและความโน้มเอียงที่จะทำลายพฤติกรรมของโปรแกรมใด ๆ ที่พยายามเข้าถึงหน่วยความจำในลักษณะที่ขัดกับกฎเหล่านั้นโดยพลการ

ได้รับ:

unsigned int a = 0x12345678;
unsigned short p = (unsigned short *)&a;
printf("0x%04X",*p);

การใช้งานบางอย่างอาจพิมพ์ 0x1234 และอื่น ๆ อาจพิมพ์ 0x5678 แต่ภายใต้มาตรฐาน C99 มันจะถูกกฎหมายสำหรับการใช้งานในการพิมพ์ "FRINK RULES!" หรือทำสิ่งอื่นใดตามทฤษฎีที่ว่าจะถูกกฎหมายสำหรับตำแหน่งหน่วยความจำที่ถือaรวมฮาร์ดแวร์ที่บันทึกประเภทที่ใช้ในการเขียนและฮาร์ดแวร์ดังกล่าวเพื่อตอบสนองต่อความพยายามในการอ่านที่ไม่ถูกต้องไม่ว่าในกรณีใด ๆ "กฎ FRINK!" ที่จะออก

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

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

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


1
การกล่าวถึงพฤติกรรมที่ไม่ได้กำหนดเมื่อพิมพ์การตัดให้คนที่ไม่เข้าใจว่า RTTI นั้นดูเหมือนเคาน์เตอร์หรือไม่
โคลจอห์นสัน

@ColeJohnson: มันเลวร้ายเกินไปไม่มีชื่อทางการหรือมาตรฐานสำหรับภาษาถิ่นของ C ที่สนับสนุนโดยคอมไพเลอร์ก่อนปี 2009 99% เนื่องจากจากทั้งมุมมองการสอนและในทางปฏิบัติพวกเขาควรพิจารณาภาษาที่แตกต่างกันโดยพื้นฐาน เนื่องจากชื่อเดียวกันได้รับทั้งภาษาถิ่นที่พัฒนาพฤติกรรมที่คาดเดาได้และปรับเปลี่ยนได้ในระยะเวลา 35 ปีภาษาถิ่นที่ทำให้เกิดพฤติกรรมดังกล่าวเพื่อจุดประสงค์ในการปรับให้เหมาะสมจึงยากที่จะหลีกเลี่ยงความสับสนเมื่อพูดถึงสิ่งที่ทำงานต่างกัน .
supercat

ในอดีต C วิ่งบนเครื่อง Lisp ที่ไม่อนุญาตให้เล่นแบบหลวม ๆ ฉันค่อนข้างมั่นใจว่าพฤติกรรม "ที่คาดเดาได้และปรับให้เหมาะสม" จำนวนมากที่เห็นเมื่อ 30 ปีก่อนนั้นไม่ได้ทำงานที่ใด แต่ BSD Unix บน VAX
prosfilaes

@prosfilaes: บางที "99% ของคอมไพเลอร์ที่ใช้ตั้งแต่ปี 1999 ถึง 2009" จะแม่นยำกว่านี้หรือไม่ แม้ว่าคอมไพเลอร์จะมีตัวเลือกสำหรับการเพิ่มประสิทธิภาพจำนวนเต็มค่อนข้างก้าวร้าว ฉันไม่รู้ว่าฉันเคยเห็นคอมไพเลอร์มาก่อนปี 1999 ซึ่งไม่มีโหมดที่ไม่รับประกันว่าint x,y,z;การแสดงออกx*y > zจะไม่ทำสิ่งใดนอกจากการคืนค่า 1 หรือ 0 หรือการละเมิดนามแฝงจะมีผลกระทบใด ๆ นอกเหนือจากการให้คอมไพเลอร์ส่งคืนค่าเก่าหรือใหม่โดยพลการ
supercat

1
... โดยที่unsigned charค่าที่ใช้สร้างประเภท "มาจาก" ถ้าโปรแกรมจะย่อยสลายตัวชี้ไปที่unsigned char[]แสดงเนื้อหาunsigned char[]เลขฐานสิบหกสั้น ๆ บนหน้าจอแล้วลบตัวชี้และหลังจากนั้นยอมรับตัวเลขฐานสิบหกบางส่วนจากแป้นพิมพ์คัดลอกกลับไปยังตัวชี้และจากนั้นตรวจหาตัวชี้นั้น พฤติกรรมจะได้รับการกำหนดอย่างดีในกรณีที่จำนวนที่ถูกพิมพ์ตรงกับจำนวนที่แสดง
supercat

3

ใน C มันไม่ได้เป็น ภาษาอื่น (เช่น Lisp, Python) มีประเภทแบบไดนามิก แต่ C เป็นแบบคงที่ นั่นหมายความว่าโปรแกรมของคุณจะต้องรู้ว่าข้อมูลชนิดใดที่จะต้องตีความอย่างถูกต้องว่าเป็นตัวละครจำนวนเต็ม ฯลฯ

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


สิ่งที่ฉันไม่เข้าใจคือคอมพิวเตอร์รู้ได้อย่างไรว่ามันช่วยให้เมื่ออ่านค่าของตัวแปรจากและที่อยู่เช่น 10001 ถ้าเป็น int หรืออักขระ ลองนึกภาพฉันคลิกที่โปรแกรมที่ชื่อว่า anyprog.exe รหัสทันทีเริ่มดำเนินการ ไฟล์ exe นี้มีข้อมูลเกี่ยวกับว่าตัวแปรถูกเก็บไว้ในหรือไม่? -
user16307

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

1
โปรดทราบว่าการพิมพ์แบบไดนามิกไม่ได้เป็นเพียงเหตุผลในการรักษาประเภท Java ถูกพิมพ์แบบสแตติก แต่ก็ยังคงต้องรักษาประเภทเพราะมันช่วยให้สะท้อนประเภทแบบไดนามิก นอกจากนี้ยังมีความแตกต่างรันไทม์คือวิธีการจัดส่งขึ้นอยู่กับประเภทรันไทม์ซึ่งมันก็ต้องการประเภท C ++ วางวิธีการส่งรหัสลงในวัตถุ (หรือค่อนข้างคลาส) ตัวเองดังนั้นจึงไม่จำเป็นต้องพิมพ์ในบางแง่มุม (แม้ว่าแน่นอน vtable อยู่ในความรู้สึกบางส่วนของประเภทดังนั้นอย่างน้อยส่วนหนึ่งของ ประเภทจะถูกเก็บไว้) แต่ใน Java รหัสการส่งวิธีการเป็นแบบรวมศูนย์
Jörg W Mittag

ดูคำถามของฉันฉันเขียน "เมื่อโปรแกรม C รัน?" พวกเขาไม่ได้จัดเก็บทางอ้อมในไฟล์ exe ระหว่างรหัสการเรียนการสอนและในที่สุดก็เกิดขึ้นในหน่วยความจำ? ฉันเขียนสิ่งนี้ให้คุณอีกครั้ง: หาก CPU พบ 0x00000061 ในการลงทะเบียนและดึงข้อมูลกลับมา; และจินตนาการว่าโปรแกรมคอนโซลควรให้ผลลัพธ์นี้เป็นอักขระที่ไม่ได้ใช้งานได้ มีอยู่ในไฟล์ exe นั้น (เครื่อง / รหัสไบนารี่) บางรหัสการเรียนการสอนที่รู้ที่อยู่ของ 0x00000061 เป็นอักขระและแปลงเป็นอักขระโดยใช้ตาราง ASCII หรือไม่ ถ้าเป็นเช่นนั้นมันหมายถึงตัวระบุถ่านภายในโดยทางอ้อมในไบนารี ???
user16307

หากค่าคือ 0x61 และถูกประกาศเป็นอักขระ char (เช่น 'a') และคุณเรียกรูทีนเพื่อแสดงค่านั้นจะมี [ในที่สุด] เป็นการเรียกระบบเพื่อแสดงอักขระนั้น หากคุณประกาศว่าเป็น int และเรียกรูทีนการแสดงคอมไพเลอร์จะรู้ว่าสร้างโค้ดเพื่อแปลง 0x61 (ทศนิยม 97) เป็นลำดับ ASCII 0x39, 0x37 ('9', '7') Bottom line: รหัสที่สร้างขึ้นจะแตกต่างกันเพราะคอมไพเลอร์รู้วิธีปฏิบัติต่างกัน
Mike Harris

3

คุณต้องแยกความแตกต่างระหว่างcompiletimeและruntimeในมือข้างหนึ่งcodeและdataในมืออื่น ๆ

จากมุมมองของเครื่องมันเป็นความแตกต่างระหว่างสิ่งที่คุณเรียกไม่codeหรือและสิ่งที่คุณเรียกinstructions dataทุกอย่างลงมาเป็นตัวเลข แต่ลำดับบางอย่าง - สิ่งที่เราจะเรียกcode- ทำสิ่งที่เราพบว่ามีประโยชน์อื่น ๆ ก็แค่crashเครื่อง

งานที่ทำโดย CPU นั้นเป็นลูป 4 ขั้นตอนง่าย ๆ :

  • ดึง "ข้อมูล" จากที่อยู่ที่ระบุ
  • ถอดรหัสคำสั่ง (เช่น "ตีความ" ตัวเลขเป็นinstruction)
  • อ่านที่อยู่ที่มีประสิทธิภาพ
  • ดำเนินการและเก็บผลลัพธ์

นี้เรียกว่าวงจรการเรียนการสอน

ฉันอ่านว่า A และ 4 ถูกเก็บไว้ในที่อยู่ RAM ที่นี่ แต่ a และ x เป็นอย่างไร?

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

การประหารชีวิตรู้ได้อย่างไรว่า a เป็นตัวละครและ x เป็น int

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

printfฟังก์ชั่ถูกออกแบบมาเพื่อ "รู้" สิ่งที่ชนิดของการป้อนข้อมูลคุณจะวางลงในนั้นคือรหัสที่เกิดของตนให้คำแนะนำที่ถูกต้องวิธีการจัดการกับหน่วยความจำส่วนพิเศษ แน่นอนว่ามันเป็นไปได้ที่จะ gnerate เอาต์พุตไร้สาระ: การใช้แอดเดรสโดยที่ไม่มีการจัดเก็บสตริงพร้อมกับ "% s" printf()จะส่งผลให้เอาต์พุตไร้สาระหยุดลงโดยตำแหน่งหน่วยความจำแบบสุ่มเท่านั้นซึ่งเป็น0 ( \0)

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

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

ดังที่ได้กล่าวไว้: "บริบท" - เช่นคำแนะนำก่อนหน้าและถัดไป - ช่วยรักษาข้อมูลในแบบที่เราต้องการ จากมุมมองของเครื่องไม่มีความแตกต่างในตำแหน่งหน่วยความจำใด ๆ intและcharเป็นเพียงคำศัพท์ซึ่งจะทำให้ความรู้สึกในการcompiletime; ในช่วงruntime(ในระดับการชุมนุม) ไม่มีหรือcharint

สิ่งที่ฉันไม่เข้าใจคือวิธีที่คอมพิวเตอร์รู้เมื่อมันอ่านค่าของตัวแปรจากที่อยู่เช่น 10001 ไม่ว่าจะเป็น int หรืออักขระ

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

ไฟล์ที่สามารถใช้งานได้นี้รวมถึงข้อมูลว่าตัวแปรที่จัดเก็บนั้นเป็นประเภท int หรืออักขระถ่านหรือไม่

ใช่และไม่มี ข้อมูลไม่ว่าจะเป็นintหรือcharหายไป แต่ในทางกลับกันบริบท (คำแนะนำที่บอกวิธีจัดการกับตำแหน่งหน่วยความจำที่จัดเก็บข้อมูล) ดังนั้นimplicitelyใช่ "ข้อมูล" เป็นimplicitelyใช้ได้


ความแตกต่างที่ดีระหว่างเวลาคอมไพล์และรันไทม์
Michael Blackburn

2

ให้เราคุยกับภาษาCเท่านั้น

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

char a = 'A';
int x = 4;

ให้เราลองวิเคราะห์แต่ละส่วน:

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

a / xรู้จักกันในชื่อ identifiers ตอนนี้คุณสามารถพูดชื่อผู้ใช้ที่เป็นมิตรกับตำแหน่งหน่วยความจำใน RAM

=บอกคอมไพเลอร์ในการจัดเก็บ 'A' ที่สถานที่ของหน่วยความจำaและ 4 xที่ตั้งของหน่วยความจำ

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


ตัวระบุชนิดข้อมูล int / char ok ไม่ได้ถูกเก็บไว้ในหน่วยความจำโดยตรงเป็นตัวแปร แต่ไม่ได้เก็บไว้โดยอ้อมในไฟล์ exe ระหว่างรหัสการเรียนการสอนและในที่สุดก็เกิดขึ้นในหน่วยความจำ? ฉันเขียนสิ่งนี้ให้คุณอีกครั้ง: หาก CPU พบ 0x00000061 ในการลงทะเบียนและดึงข้อมูลกลับมา; และจินตนาการว่าโปรแกรมคอนโซลควรให้ผลลัพธ์นี้เป็นอักขระที่ไม่ได้ใช้งานได้ มีอยู่ในไฟล์ exe นั้น (เครื่อง / รหัสไบนารี่) บางรหัสการเรียนการสอนที่รู้ที่อยู่ของ 0x00000061 เป็นอักขระและแปลงเป็นอักขระโดยใช้ตาราง ASCII หรือไม่ ถ้าเป็นเช่นนั้นมันหมายถึงตัวระบุถ่านภายในโดยทางอ้อมในไบนารี ???
user16307

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

2

คำตอบของฉันที่นี่ค่อนข้างง่ายและจะอ้างถึง C เท่านั้น

ไม่ข้อมูลประเภทจะไม่ถูกเก็บไว้ในโปรแกรม

intหรือcharไม่ใช่ตัวบ่งชี้ประเภทของ CPU; เฉพาะคอมไพเลอร์

exe ที่สร้างขึ้นโดยคอมไพเลอร์จะมีคำแนะนำในการจัดการints intถ้าตัวแปรถูกประกาศให้เป็น ในทำนองเดียวกันถ้าตัวแปรที่ถูกประกาศเป็นchar, exe charจะมีคำแนะนำในการจัดการกับ

ใน C:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

โปรแกรมนี้จะพิมพ์ข้อความเนื่องจากcharและintมีค่าเดียวกันใน RAM

ตอนนี้หากคุณสงสัยว่าprintfจะจัดการเอาต์พุต65สำหรับ a intและAa ได้charอย่างไรนั่นก็เพราะคุณต้องระบุใน "format string" ว่าprintfควรจัดการกับค่าอย่างไร
(ตัวอย่างเช่น%cหมายถึงการปฏิบัติต่อค่าเป็น a charและ%dหมายถึงการปฏิบัติต่อค่าเป็นจำนวนเต็ม; ค่าเดียวกันทั้งสองวิธี)


2
printfผมก็หวังว่าคนจะใช้ตัวอย่างในการใช้ @OP: ออกจะint a = 65; printf("%c", a) 'A'ทำไม? เพราะโปรเซสเซอร์ไม่สนใจ ไปที่มันทั้งหมดเห็นเป็นบิต โปรแกรมของคุณบอกหน่วยประมวลผลให้เก็บ 65 (โดยบังเอิญค่าเป็น'A'ASCII) ที่aและจากนั้นออกอักขระซึ่งยินดีทำ ทำไม? เพราะมันไม่สนใจ
โคลจอห์นสัน

แต่ทำไมบางคนบอกว่าที่นี่ในกรณี C # มันไม่ใช่เรื่อง? ฉันอ่านความคิดเห็นอื่น ๆ และพวกเขาบอกว่าใน C # และ C ++ เรื่องราว (ข้อมูลเกี่ยวกับประเภทข้อมูล) แตกต่างกันและแม้กระทั่ง CPU ไม่ได้ทำการคำนวณ มีความคิดเห็นเกี่ยวกับอะไร
user16307

@ user16307 หาก CPU ไม่ทำการคำนวณโปรแกรมจะไม่ทำงาน :) สำหรับ C # ฉันไม่รู้ แต่ฉันคิดว่าคำตอบของฉันก็มีเช่นกัน สำหรับ C ++ ฉันรู้ว่าคำตอบของฉันใช้ได้ที่นั่น
BenjiWiebe

0

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

นั่นคือทั้งหมดที่ซีพียูเคยทำได้ทุกอย่างที่ทำได้ ไม่มีสิ่งเช่น int หรือ char

x = 4 + 5

จะทำงานเป็น:

  1. โหลด 00000100 ลงทะเบียน 1
  2. โหลด 00000101 ลงทะเบียน 2
  3. IAdd register 1 เพื่อ register 2 และเก็บใน register 1

คำสั่ง iadd ทริกเกอร์ฮาร์ดแวร์ซึ่งทำหน้าที่ราวกับว่ารีจิสเตอร์ 1 และ 2 เป็นจำนวนเต็ม หากพวกเขาไม่ได้เป็นตัวแทนของจำนวนเต็มทุกสิ่งสามารถผิดพลาดได้ในภายหลัง ผลลัพธ์ที่ดีที่สุดมักจะล้มเหลว

มันอยู่ในคอมไพเลอร์เพื่อเลือกคำสั่งที่ถูกต้องตามประเภทที่กำหนดในแหล่งที่มา แต่ในรหัสเครื่องจริงที่ดำเนินการโดย CPU ไม่มีประเภทใด ๆ

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


0

คำตอบสั้น ๆ ประเภทจะถูกเข้ารหัสในคำสั่ง CPU ที่คอมไพเลอร์สร้างขึ้น

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

การประมวลผลรู้ได้อย่างไรว่า a เป็น char และ x เป็น int หรือไม่

มันไม่ได้ แต่เมื่อคอมไพเลอร์ผลิตรหัสเครื่องมันรู้ intและcharสามารถขนาดแตกต่างกัน ในสถาปัตยกรรมที่ char มีขนาดของไบต์และ int คือ 4 ไบต์ตัวแปรxจะไม่อยู่ในที่อยู่ 1,0001 แต่ยังอยู่ใน 10002, 10003 และ 10004 เมื่อโค้ดจำเป็นต้องโหลดค่าxลงใน CPU register มันใช้คำสั่งสำหรับการโหลด 4 ไบต์ เมื่อโหลดถ่านมันใช้คำแนะนำในการโหลด 1 ไบต์

วิธีการเลือกหนึ่งในสองคำแนะนำ? คอมไพเลอร์ตัดสินใจในระหว่างการรวบรวมมันไม่ได้ทำที่รันไทม์หลังจากตรวจสอบค่าในหน่วยความจำ

โปรดทราบว่าการลงทะเบียนอาจมีขนาดแตกต่างกัน บน Intel x86 CPUs EAX มีความกว้าง 32 บิตครึ่งหนึ่งคือ AX ซึ่งคือ 16 และ AX แบ่งออกเป็น AH และ AL ทั้ง 8 บิต

ดังนั้นหากคุณต้องการโหลดจำนวนเต็ม (บน x86 CPUs) คุณใช้คำสั่ง MOV สำหรับจำนวนเต็มเพื่อโหลด char คุณใช้คำสั่ง MOV สำหรับตัวอักษร พวกเขาทั้งสองเรียกว่า MOV แต่มีรหัส op แตกต่างกัน มีประสิทธิภาพเป็นสองคำแนะนำที่แตกต่างกัน ชนิดของตัวแปรนั้นถูกเข้ารหัสในคำสั่งที่จะใช้

สิ่งเดียวกันนี้เกิดขึ้นกับการปฏิบัติการอื่น ๆ มีคำแนะนำมากมายสำหรับการเพิ่มนอกจากนี้ขึ้นอยู่กับขนาดของตัวถูกดำเนินการและแม้ว่าจะถูกลงชื่อหรือไม่ได้ลงชื่อก็ตาม ดูhttps://en.wikipedia.org/wiki/ADD_(x86_instruction)ซึ่งแสดงรายการเพิ่มเติมที่แตกต่างกัน

สมมติว่าค่าถูกเก็บไว้ที่ใดที่หนึ่งใน RAM เช่น 10011001 ถ้าฉันเป็นโปรแกรมที่รันโค้ดฉันจะรู้ได้อย่างไรว่า 10011001 นี้เป็น char หรือ int

ขั้นแรกตัวอักษรจะเท่ากับ 10011001 แต่ตัวอักษรจะเป็น 00000000 00000000 00000000 10011001 เนื่องจากมีขนาดแตกต่างกัน (ในคอมพิวเตอร์ที่มีขนาดเท่าที่กล่าวถึงข้างต้น) แต่ช่วยให้พิจารณากรณีสำหรับVSsigned charunsigned char

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


แต่ทำไมบางคนบอกว่าที่นี่ในกรณี C # มันไม่ใช่เรื่อง? ฉันอ่านความคิดเห็นอื่น ๆ และพวกเขาบอกว่าใน C # และ C ++ เรื่องราว (ข้อมูลเกี่ยวกับประเภทข้อมูล) แตกต่างกันและแม้กระทั่ง CPU ไม่ได้ทำการคำนวณ มีความคิดเห็นเกี่ยวกับอะไร
user16307

0

แต่ทำไมบางคนบอกว่าที่นี่ในกรณี C # มันไม่ใช่เรื่อง? ฉันอ่านความคิดเห็นอื่น ๆ และพวกเขาบอกว่าใน C # และ C ++ เรื่องราว (ข้อมูลเกี่ยวกับประเภทข้อมูล) จะแตกต่างกันและแม้กระทั่ง CPU ไม่ได้ทำการคำนวณ มีความคิดเห็นเกี่ยวกับอะไร

ในภาษาที่ตรวจสอบประเภทเช่น C # การตรวจสอบประเภทจะกระทำโดยคอมไพเลอร์ รหัส benji เขียนว่า:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

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

int a = 42;
string b = "Compilers are awesome.";
double[] c = a * b;

คอมไพเลอร์จะปฏิเสธที่จะสร้างรหัสเครื่องจาก C # นี้ไม่ว่าสตริงของคุณจะจูบกันมากแค่ไหนก็ตาม


-4

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

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


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

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

2
ฉันคิดว่าคำตอบนี้ไม่เป็นประโยชน์ มันทำให้สิ่งต่าง ๆ ซับซ้อนเกินกว่าระดับความเข้าใจในปัจจุบันของ OP เห็นได้ชัดว่า OP ไม่เข้าใจรูปแบบการดำเนินการขั้นพื้นฐานของ CPU + RAM และคอมไพเลอร์แปลแหล่งที่มาระดับสูงสัญลักษณ์ไปเป็นไบนารีที่ปฏิบัติการได้อย่างไร หน่วยความจำที่ติดแท็ก RTTI เสียงกระเพื่อม ฯลฯ เป็นวิธีที่เกินกว่าสิ่งที่ผู้ถามจำเป็นต้องรู้ในความเห็นของฉันและจะทำให้เขา / เธอสับสนมากขึ้นเท่านั้น
Andres F.

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