ทำไมประเภทมีขนาดที่แน่นอนเสมอไม่ว่าจะมีคุณค่าอย่างไร


149

การใช้งานอาจแตกต่างกันระหว่างขนาดที่แท้จริงของประเภท แต่โดยส่วนใหญ่ประเภทเช่น int ที่ไม่ได้ลงนามและ float จะมีขนาด 4 ไบต์เสมอ แต่ทำไมประเภทหนึ่งครอบครองหน่วยความจำจำนวนหนึ่งเสมอ ตัวอย่างเช่นถ้าฉันสร้างจำนวนเต็มต่อไปนี้ด้วยค่า 255

int myInt = 255;

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


15
1) " อย่างไรก็ตามค่าจริง 256 สามารถแสดงที่มีเพียง 1 ไบต์ " ผิดที่ใหญ่ที่สุดunsingedคุ้มค่าที่สามารถแทนด้วย 1 255ไบต์คือ 2) พิจารณาค่าใช้จ่ายในการคำนวณขนาดของที่เก็บข้อมูลที่เหมาะสมและลดขนาด / ขยายพื้นที่เก็บข้อมูลของตัวแปรเมื่อค่าเปลี่ยนแปลง
Algirdas Preidžius

99
เมื่อถึงเวลาอ่านค่าจากหน่วยความจำคุณจะเสนอเครื่องจะกำหนดจำนวนไบต์ที่จะอ่านได้อย่างไร เครื่องจะทราบได้อย่างไรว่าจะหยุดอ่านค่าได้อย่างไร สิ่งนี้จะต้องมีสิ่งอำนวยความสะดวกเพิ่มเติม และในกรณีทั่วไปหน่วยความจำและค่าใช้จ่ายในการปฏิบัติงานสำหรับสิ่งอำนวยความสะดวกเพิ่มเติมเหล่านี้จะสูงกว่าในกรณีของการแก้ไขเพียงแค่ใช้ 4 ไบต์สำหรับunsigned intค่า
AnT

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

37
พิจารณาสิ่งที่จะเกิดขึ้นหากคุณเพิ่ม 1 ลงในค่าของตัวแปรทำให้เป็น 256 ดังนั้นจึงจำเป็นต้องขยาย มันขยายไปถึงที่ไหน? คุณย้ายหน่วยความจำที่เหลือเพื่อเพิ่มพื้นที่หรือไม่? ตัวแปรเคลื่อนตัวเองหรือไม่? ถ้าเป็นเช่นนั้นจะย้ายไปที่ใดและคุณจะพบตัวชี้ที่คุณต้องการอัปเดตได้อย่างไร
molbdnilo

13
@someidiot ไม่คุณผิด std::vector<X>มักจะมีขนาดเท่ากันคือsizeof(std::vector<X>)ค่าคงที่เวลารวบรวม
SergeyA

คำตอบ:


131

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

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

ให้พิจารณาสถาปัตยกรรมเครื่องเฉพาะ เรามาดูตระกูล Intel x86 ปัจจุบันกัน

คู่มือสำหรับนักพัฒนาซอฟท์แวร์สถาปัตยกรรมIntel® 64 และ IA-32 เล่ม 1 ( ลิงค์ ) ส่วน 3.4.1 กล่าวว่า

วัตถุประสงค์ทั่วไป 32- บิตลงทะเบียน EAX, EBX, ECX, EDX, ESI, EDI, EBP และ ESP มีไว้สำหรับการถือครองรายการต่อไปนี้:

•ดำเนินการสำหรับการดำเนินการทางตรรกะและเลขคณิต

•ดำเนินการสำหรับการคำนวณที่อยู่

•ตัวชี้หน่วยความจำ

ดังนั้นเราต้องการให้คอมไพเลอร์ใช้ EAX, EBX และอื่น ๆ ลงทะเบียนเมื่อคอมไพล์เลขจำนวนเต็ม C ++ อย่างง่าย ซึ่งหมายความว่าเมื่อฉันประกาศintมันควรเป็นสิ่งที่เข้ากันได้กับการลงทะเบียนเหล่านี้เพื่อให้ฉันสามารถใช้พวกเขาได้อย่างมีประสิทธิภาพ

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

การใช้godboltเราสามารถเห็นได้อย่างชัดเจนว่าคอมไพเลอร์ทำอะไรกับโค้ดเล็ก ๆ น้อย ๆ :

int square(int num) {
    return num * num;
}

คอมไพล์ (ด้วย GCC 8.1 และ-fomit-frame-pointer -O3เพื่อความง่าย) ไปที่:

square(int):
  imul edi, edi
  mov eax, edi
  ret

หมายความว่า:

  1. int numพารามิเตอร์ก็ผ่านไปได้ในการลงทะเบียน EDI ความหมายมันว่าขนาดและรูปแบบของอินเทลคาดหวังสำหรับการลงทะเบียนพื้นเมือง ฟังก์ชั่นไม่ต้องแปลงอะไรเลย
  2. การคูณเป็นคำสั่งเดียว ( imul) ซึ่งเร็วมาก
  3. การส่งคืนผลลัพธ์เป็นเพียงเรื่องของการคัดลอกไปยังการลงทะเบียนอื่น (ผู้โทรคาดว่าจะได้ผลลัพธ์ที่จะใส่ใน EAX)

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

การใช้godboltอีกครั้งเราสามารถเปรียบเทียบการคูณพื้นเมืองอย่างง่าย

unsigned mult (unsigned x, unsigned y)
{
    return x*y;
}

mult(unsigned int, unsigned int):
  mov eax, edi
  imul eax, esi
  ret

ด้วยรหัสเทียบเท่าสำหรับความกว้างที่ไม่ได้มาตรฐาน

struct pair {
    unsigned x : 31;
    unsigned y : 31;
};

unsigned mult (pair p)
{
    return p.x*p.y;
}

mult(pair):
  mov eax, edi
  shr rdi, 32
  and eax, 2147483647
  and edi, 2147483647
  imul eax, edi
  ret

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

ความซับซ้อนพิเศษนี้หมายความว่าคุณจะต้องใส่ใจกับสิ่งนี้เมื่อการประหยัดพื้นที่สำคัญมาก ในกรณีนี้เราประหยัดเพียงสองบิตเมื่อเทียบกับการใช้เนทิฟunsignedหรือuint32_tประเภทซึ่งจะสร้างรหัสที่ง่ายกว่ามาก


หมายเหตุเกี่ยวกับขนาดแบบไดนามิก:

ตัวอย่างข้างต้นยังคงเป็นค่าความกว้างคงที่มากกว่าความกว้างผันแปร แต่ความกว้าง (และการจัดตำแหน่ง) ไม่ตรงกับการลงทะเบียนดั้งเดิม

แพลตฟอร์ม x86 มีหลายขนาดรวมถึง 8 บิตและ 16 บิตนอกเหนือจากหลัก 32 บิต (ฉันกำลังคัดสรรโหมด 64 บิตและสิ่งอื่น ๆ เพื่อความเรียบง่าย)

ประเภทนี้ (ถ่าน, int8_t, uint8_t, int16_t ฯลฯ ) นอกจากนี้ยังได้รับการสนับสนุนโดยตรงจากสถาปัตยกรรม - ส่วนหนึ่งหลังเข้ากันได้กับรุ่นเก่า 8086/286/386 / etc ชุดคำสั่ง ฯลฯ

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

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


หมายเหตุเพิ่มเติมเกี่ยวกับประสิทธิภาพ

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

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

การลงโทษด้วยความเร็วที่คุณจ่ายสำหรับสิ่งนี้หมายความว่ามันคุ้มค่าเมื่อคุณต้องการลดแบนด์วิดท์หรือการจัดเก็บข้อมูลระยะยาวและสำหรับกรณีเหล่านี้มักจะใช้รูปแบบที่เรียบง่ายและเป็นธรรมชาติได้ง่ายกว่า - จากนั้นบีบอัดด้วยระบบอเนกประสงค์ (เช่น zip, gzip, bzip2, xy หรืออะไรก็ตาม)


TL; DR

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


ฉันกำลังมองหาคำตอบที่ดีทั้งหมดในขณะที่พยายามทำความเข้าใจกับพวกเขาทั้งหมด .. ดังนั้นในส่วนที่เกี่ยวกับคำตอบของคุณจะไม่มีขนาดแบบไดนามิกพูดน้อยกว่า 32 บิตสำหรับจำนวนเต็มไม่ใช่แค่อนุญาตให้มีตัวแปรเพิ่มเติมภายในทะเบียน ? หากความรักเหมือนกันเหตุใดจึงไม่ดีที่สุด
Nichlas Uden

7
@asd แต่คุณจะใช้จำนวนการลงทะเบียนในรหัสที่ระบุจำนวนตัวแปรที่ถูกเก็บไว้ในการลงทะเบียนในปัจจุบัน?
user253751

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

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

1
+1 หมายเหตุเกี่ยวกับ "เรียบง่ายและรูปแบบธรรมชาติแล้วการบีบอัด" เป็นมักจะดีกว่า: นี้แน่นอนจริงโดยทั่วไป , แต่เพราะข้อมูลบางอย่าง VLQ แต่ละมูลค่าแล้วบีบอัดที่ทั้งสิ่งที่ดำเนินสะดุดตาดีกว่าเพียงแค่การบีบอัดที่ - ทั้งสิ่งและสำหรับบางแอปพลิเคชันข้อมูลของคุณไม่สามารถบีบอัดกันได้เพราะมันอาจแตกต่างกัน (เหมือนในgitเมทาดาทา) หรือคุณกำลังเก็บไว้ในหน่วยความจำจำเป็นต้องเข้าถึงหรือปรับเปลี่ยนแบบสุ่ม แต่ไม่มากที่สุด ค่า (เช่นในเอ็นจิ้นการเรนเดอร์ HTML + CSS) และสามารถ shunken โดยใช้บางอย่างเช่น VLQ in-place
mtraceur

139

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

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

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


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

5
"เพราะประเภทแสดงถึงพื้นที่จัดเก็บพื้นฐาน"สิ่งนี้ไม่เป็นความจริงสำหรับทุกภาษา (เช่นตัว
พิมพ์ดีด

56
@ แท็ก corvus_192 มีความหมาย คำถามนี้ถูกแท็กด้วย C ++ ไม่ใช่ 'typescript'
SergeyA

4
@ ahouse101 อันที่จริงมีหลายภาษาที่มีจำนวนเต็มไม่ จำกัด ที่มีความแม่นยำพวกเขาเติบโตได้ตามต้องการ ภาษาเหล่านี้ไม่ต้องการให้คุณจัดสรรหน่วยความจำคงที่สำหรับตัวแปร แต่จะใช้ภายในเป็นการอ้างอิงวัตถุ ตัวอย่าง: Lisp, Python
Barmar

2
@jamesqf อาจไม่ใช่เรื่องบังเอิญที่ MP ทางคณิตศาสตร์ได้ถูกนำมาใช้เป็นครั้งแรกใน Lisp ซึ่งยังทำการจัดการหน่วยความจำอัตโนมัติ นักออกแบบรู้สึกว่าผลกระทบด้านประสิทธิภาพเป็นเรื่องรองลงมาจากความง่ายในการเขียนโปรแกรม และเทคนิคการเพิ่มประสิทธิภาพได้รับการพัฒนาเพื่อลดผลกระทบ
Barmar

44

เป็นการเพิ่มประสิทธิภาพและทำให้เข้าใจง่าย

คุณสามารถมีวัตถุขนาดคงที่ได้ ดังนั้นการจัดเก็บค่า
หรือคุณสามารถมี objets ขนาดตัวแปร แต่การเก็บค่าและขนาด

วัตถุขนาดคงที่

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

วัตถุขนาดไดนามิก

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

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

สรุป

รหัสที่สร้างขึ้นสำหรับวัตถุขนาดคงที่นั้นง่ายกว่ามาก

บันทึก

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


4
นี่คือคำตอบที่ดีที่สุดสำหรับฉัน: คุณจะติดตามขนาดได้อย่างไร มีหน่วยความจำเพิ่มเติมหรือไม่
ออนไลน์ Thomas

@ThomasMoors ใช่แน่นอน: มีหน่วยความจำมากขึ้น หากคุณเช่นมีอาร์เรย์แบบไดนามิกแล้วบางส่วนintจะจัดเก็บจำนวนองค์ประกอบในอาร์เรย์นั้น ว่าintตัวเองจะมีขนาดคงที่อีกครั้ง
Alfe

1
@ThomasMoors มีสองตัวเลือกที่ใช้กันทั่วไปซึ่งทั้งสองอย่างนี้ต้องการหน่วยความจำเพิ่มเติม - คุณมีฟิลด์ (ขนาดคงที่) บอกคุณว่ามีข้อมูลมากแค่ไหน (เช่น int สำหรับขนาดอาร์เรย์หรือสตริง "pascal-style") องค์ประกอบประกอบด้วยจำนวนอักขระที่มี) หรืออีกวิธีหนึ่งคุณสามารถมีห่วงโซ่ (หรือโครงสร้างที่ซับซ้อนมากขึ้น) ซึ่งองค์ประกอบแต่ละรายการจะบันทึกถ้าเป็นตัวสุดท้าย - เช่นสตริงที่สิ้นสุดด้วย Zero หรือรูปแบบรายการที่เชื่อมโยงส่วนใหญ่
Peteris

27

เพราะในภาษาอย่าง C ++ เป้าหมายการออกแบบก็คือการใช้งานที่เรียบง่ายนั้นจะรวบรวมคำสั่งของเครื่องอย่างง่าย

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

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

ลองจินตนาการว่าคอมพิวเตอร์เป็นเทป

| xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | ...

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

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

ประเภทของภาษาที่มีความกว้างคงที่เช่น C และ C ++ สะท้อนถึงสิ่งนั้น

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

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


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

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

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


26

Java ใช้คลาสที่เรียกว่า "BigInteger" และ "BigDecimal" เพื่อทำสิ่งนี้เช่นเดียวกับอินเทอร์เฟซคลาส C ++ ของ C ++ GMP ชัด (ขอบคุณ Digital Trauma) คุณสามารถทำมันด้วยตัวเองในภาษาใดก็ได้ถ้าคุณต้องการ

ซีพียูมีความสามารถในการใช้BCD (Binary Coded Decimal) ซึ่งออกแบบมาเพื่อรองรับการทำงานของความยาวทุก ๆ (แต่คุณมักจะทำงานด้วยตนเองบนหนึ่งไบต์ในแต่ละครั้งซึ่งจะช้าตามมาตรฐาน GPU ปัจจุบัน)

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

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

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


4
ดีใจที่เห็นคนพูดถึง BigInteger ไม่ใช่ว่ามันเป็นความคิดที่ไร้สาระ แต่เป็นเพียงการทำให้เป็นจำนวนมากเท่านั้น
Max Barraclough

1
จะอวดความจริงคุณหมายถึงตัวเลขที่แม่นยำอย่างยิ่ง :) อย่างน้อยก็ในกรณีของ BigDecimal ...
Bill K

2
และเนื่องจากสิ่งนี้ถูกติดแท็กc ++มันอาจจะคุ้มค่าที่จะกล่าวถึงอินเทอร์เฟซของคลาส GMP C ++ซึ่งเป็นแนวคิดเดียวกับ Big * ของ Java
Digital Trauma

20

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

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

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

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

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


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


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

1
ฉันคิดค่อนข้างเกี่ยวกับการดำเนินงานเช่นการคูณตัวแปร constexpr โดยตัวแปรปกติ ตัวอย่างเช่นเรามีตัวแปร 8 หลักไบต์ที่มีค่า56และเราคูณด้วยตัวแปร 2 ไบต์ ในบางสถาปัตยกรรมการทำงาน 64- บิตจะเป็นการคำนวณที่หนักกว่าดังนั้นคอมไพเลอร์สามารถปรับให้เหมาะสมเพื่อทำการคูณ 16 บิตเท่านั้น
NO_NAME

การใช้งาน APL บางภาษาและบางภาษาในตระกูล SNOBOL (SPITBOL ฉันคิดว่าบางทีไอคอน) ทำสิ่งนี้อย่างแม่นยำ (ด้วยความละเอียด): เปลี่ยนรูปแบบการนำเสนอแบบไดนามิกขึ้นอยู่กับค่าจริง APL จะเปลี่ยนจากบูลีนเป็นจำนวนเต็มเพื่อลอยและย้อนกลับ SPITBOL จะเปลี่ยนจากการแสดงคอลัมน์ของ Booleans (8 ชุดบูลีนที่แยกจากกันซึ่งเก็บไว้ในอาร์เรย์ไบต์) เป็นจำนวนเต็ม (IIRC)
davidbak

16

จากนั้นmyIntจะครอบครอง 4 ไบต์ด้วยคอมไพเลอร์ของฉัน อย่างไรก็ตามค่าจริง255สามารถแสดงด้วย 1 ไบต์เท่านั้นดังนั้นทำไมจะmyIntไม่ใช้หน่วยความจำ 1 ไบต์

นี้เรียกว่าการเข้ารหัสความยาวตัวแปรมีการเข้ารหัสต่างๆที่กำหนดไว้เช่นVLQ อย่างไรก็ตามหนึ่งในสิ่งที่โด่งดังที่สุดอาจเป็นUTF-8 : UTF-8 เข้ารหัสจุดรหัสบนจำนวนตัวแปรไบต์ตั้งแต่ 1 ถึง 4

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

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

การออกแบบที่ได้รับการตัดสินคือการใช้ประเภทพื้นฐานขนาดคงที่และฮาร์ดแวร์ / ภาษาเพิ่งบินลงมาจากที่นั่น

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

ดัชนีของไบต์ที่จุดโค้ด 4 เริ่มต้นในสตริง UTF-8 คืออะไร

ขึ้นอยู่กับค่าของจุดรหัสก่อนหน้านี้ต้องการการสแกนเชิงเส้น

แน่นอนว่ามีรูปแบบการเข้ารหัสความยาวผันแปรซึ่งจะดีกว่าเมื่อใช้การสุ่มเลือก

ใช่ แต่มันก็ซับซ้อนกว่าเช่นกัน หากมีอุดมคติฉันก็ยังไม่เคยเห็นเลย

การสุ่มที่อยู่แบบสุ่มมีความสำคัญจริงๆหรือไม่?

โอ้ใช่!

สิ่งนี้คือการรวม / อาเรย์ชนิดใด ๆ อาศัยประเภทขนาดคงที่:

  • การเข้าถึงฟิลด์ที่ 3 ของ struct ? ที่อยู่แบบสุ่ม!
  • การเข้าถึงองค์ประกอบที่ 3 ของอาร์เรย์? ที่อยู่แบบสุ่ม!

ซึ่งหมายความว่าคุณจำเป็นต้องได้รับผลประโยชน์ดังต่อไปนี้:

ประเภทขนาดคงที่หรือการสแกนหน่วยความจำเชิงเส้น


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

2
@Artelius: คุณเข้ารหัสตารางเวกเตอร์อย่างไรเมื่อจำนวนเต็มมีความกว้างของตัวแปร นอกจากนี้ค่าใช้จ่ายหน่วยความจำของตารางเวกเตอร์คืออะไรเมื่อเข้ารหัสหนึ่งสำหรับจำนวนเต็มที่ใช้ 1 ถึง 4 ไบต์ในหน่วยความจำ
Matthieu M.

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

@Artelius: โปรดทราบว่าใน Python อาร์เรย์จะมีตัวชี้ขนาดคงที่สำหรับองค์ประกอบ สิ่งนี้ทำให้ O (1) เข้าถึงองค์ประกอบโดยเสียค่าใช้จ่ายทางอ้อม
Matthieu M.

16

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

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


"X จะไม่สามารถเติบโตได้มากกว่า N ไบต์ภายในช่วงอายุของ Y ยกเว้นว่าจะย้าย X หรือ Y อย่างใดอย่างหนึ่งเพื่อที่ X จะย้ายมันจำเป็นต้องให้ทุกสิ่งในจักรวาลที่มีที่อยู่ X ถูกอัปเดตเพื่อสะท้อน ใหม่และเช่นเดียวกันสำหรับ Y ที่จะย้าย " นี่คือจุดสำคัญ IMO: วัตถุที่ใช้ขนาดเท่าความต้องการค่าปัจจุบันของพวกเขาจะต้องเพิ่มค่าใช้จ่ายเป็นตันสำหรับขนาด / รักษาการณ์การย้ายหน่วยความจำกราฟอ้างอิง ฯลฯ และค่อนข้างชัดเจนเมื่อนักคิดคนหนึ่งทำงานได้อย่างไร ... แต่ถึงกระนั้นก็มีค่ามากที่กล่าวอย่างชัดเจนโดยเฉพาะอย่างยิ่งที่คนอื่นทำ
underscore_d

@underscore_d: ภาษาเช่น Javascript ซึ่งได้รับการออกแบบมาตั้งแต่ต้นจนถึงการจัดการกับวัตถุขนาดแปรปรวนนั้นมีประสิทธิภาพอย่างน่าอัศจรรย์ ในขณะที่เป็นไปได้ที่จะทำให้ระบบวัตถุขนาดผันแปรง่ายและเป็นไปได้ที่จะทำให้มันเร็ว แต่การใช้งานง่ายนั้นช้าและการใช้งานที่รวดเร็วนั้นซับซ้อนมาก
supercat

13

คำตอบสั้น ๆ คือ: เพราะมาตรฐาน C ++ บอกเช่นนั้น

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

อีกจุดที่ต้องพิจารณาคือหน่วยความจำคอมพิวเตอร์ทำงานอย่างไร สมมติว่าประเภทจำนวนเต็มของคุณสามารถใช้พื้นที่เก็บข้อมูลระหว่าง 1 ถึง 4 ไบต์ สมมติว่าคุณเก็บค่า 42 ไว้ในจำนวนเต็มของคุณ: ใช้เวลา 1 ไบต์และคุณวางไว้ที่ที่อยู่หน่วยความจำ X จากนั้นคุณเก็บตัวแปรถัดไปของคุณที่ตำแหน่ง X + 1 (ฉันไม่ได้พิจารณาการจัดตำแหน่งที่จุดนี้) เป็นต้น . หลังจากนั้นคุณตัดสินใจที่จะเปลี่ยนค่าของคุณเป็น 6424

แต่สิ่งนี้ไม่พอดีกับไบต์เดียว! แล้วคุณจะทำอย่างไร? คุณพักที่ไหน คุณมีบางอย่างที่ X + 1 ดังนั้นไม่สามารถวางไว้ที่นั่นได้ ที่อื่น? คุณจะรู้ได้อย่างไรในภายหลัง หน่วยความจำคอมพิวเตอร์ไม่รองรับการแทรกความหมาย: คุณไม่สามารถวางบางสิ่งบางอย่างไว้ในตำแหน่งเดียวและผลักดันทุกอย่างหลังจากนั้นเพื่อให้มีที่ว่าง!

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


11

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

นี่ไม่ใช่วิธีการที่ทำ พิจารณาโปรโตคอล Protobuf ของ Google Protobuf ถูกออกแบบมาเพื่อส่งข้อมูลอย่างมีประสิทธิภาพ การลดจำนวนไบต์ที่ส่งจะคุ้มค่ากับคำแนะนำเพิ่มเติมเมื่อทำงานกับข้อมูล ดังนั้น protobufs ใช้การเข้ารหัสที่เข้ารหัสจำนวนเต็มใน 1, 2, 3, 4, หรือ 5 ไบต์และจำนวนเต็มขนาดเล็กจะใช้จำนวนไบต์น้อยลง อย่างไรก็ตามเมื่อได้รับข้อความมันจะถูกแยกออกเป็นรูปแบบจำนวนเต็มที่มีขนาดคงที่ซึ่งง่ายต่อการใช้งาน เฉพาะในช่วงการส่งผ่านเครือข่ายที่พวกเขาใช้จำนวนเต็มความยาวแปรผันของพื้นที่อย่างมีประสิทธิภาพ


11

ฉันชอบบ้านของ Sergeyแต่ฉันคิดว่าการเปรียบเทียบรถน่าจะดีกว่า

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

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

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

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


10

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

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

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

เชิงอรรถ: เพื่อความแม่นยำมากขึ้นในขณะที่คุณระบุเป็นไบต์ระบบส่วนใหญ่จะละเว้นไบต์ 'ไม่สม่ำเสมอ' นั่นคือที่อยู่ 0, 1, 2 และ 3 ทั้งหมดอ่านคำเดียวกัน 4, 5, 6 และ 7 อ่านคำถัดไปและอื่น ๆ

นี่คือสาเหตุที่ระบบ 32 บิตมีหน่วยความจำสูงสุด 4 GB รีจิสเตอร์ที่ใช้เพื่อระบุตำแหน่งในหน่วยความจำมักมีขนาดใหญ่พอที่จะเก็บคำเช่น 4 ไบต์ซึ่งมีค่าสูงสุด (2 ^ 32) -1 = 4294967295 4294967296 ไบต์คือ 4 GB


8

มีวัตถุที่อยู่ในความรู้สึกบางอย่างมีขนาดตัวแปรใน C ++ std::vectorมาตรฐานห้องสมุดเช่น อย่างไรก็ตามสิ่งเหล่านี้ทั้งหมดจัดสรรหน่วยความจำเพิ่มเติมแบบไดนามิกที่พวกเขาต้องการ หากคุณใช้sizeof(std::vector<int>)คุณจะได้รับค่าคงที่ที่ไม่เกี่ยวข้องกับหน่วยความจำที่จัดการโดยวัตถุและถ้าคุณจัดสรรอาร์เรย์หรือโครงสร้างที่มีstd::vector<int>มันจะสำรองขนาดฐานนี้แทนที่จะวางที่เก็บข้อมูลเพิ่มเติมในอาร์เรย์หรือโครงสร้างเดียวกัน . มีบางส่วนของไวยากรณ์ C ที่สนับสนุนบางอย่างเช่นนี้อาร์เรย์และโครงสร้างที่มีความยาวต่างกันโดยเฉพาะ แต่ C ++ ไม่ได้เลือกที่จะสนับสนุน

มาตรฐานภาษากำหนดขนาดของวัตถุด้วยวิธีนั้นเพื่อให้คอมไพเลอร์สามารถสร้างรหัสที่มีประสิทธิภาพ ตัวอย่างเช่นหากintเกิดขึ้นมีความยาว 4 ไบต์ในการนำไปใช้บางอย่างและคุณประกาศaเป็นตัวชี้ไปยังหรืออาร์เรย์ของintค่าให้a[i]แปลเป็น pseudocode“ อ้างอิงที่อยู่ + 4 × i” สิ่งนี้สามารถทำได้ในเวลาที่คงที่และเป็นการดำเนินการทั่วไปและสำคัญที่สถาปัตยกรรมชุดคำสั่งจำนวนมากรวมถึงเครื่อง x86 และ DEC PDP ที่ C พัฒนาขึ้นในตอนแรกสามารถทำได้ในการเรียนการสอนด้วยเครื่องเดียว

ตัวอย่างหนึ่งในโลกแห่งความจริงที่พบบ่อยของข้อมูลที่จัดเก็บอย่างต่อเนื่องเป็นหน่วยความยาวผันแปรได้คือสตริงที่เข้ารหัสเป็น UTF-8 (อย่างไรก็ตามประเภทพื้นฐานของสตริง UTF-8 ที่คอมไพเลอร์ยังคงอยู่charและมีความกว้าง 1 ซึ่งอนุญาตให้ตีความสตริง ASCII เป็น UTF-8 ที่ถูกต้องและรหัสไลบรารีจำนวนมากเช่นstrlen()และstrncpy()ทำงานต่อไป) การเข้ารหัส codepoint UTF-8 ใด ๆ สามารถมีความยาวหนึ่งถึงสี่ไบต์และดังนั้นหากคุณต้องการ codepoint UTF-8 ตัวที่ห้าในสตริงคุณสามารถเริ่มต้นที่ใดก็ได้จากไบต์ที่ห้าถึงไบต์ที่สิบเจ็ดของข้อมูล วิธีเดียวในการค้นหาคือการสแกนจากจุดเริ่มต้นของสายอักขระและตรวจสอบขนาดของแต่ละ codepoint หากคุณต้องการค้นหากราฟที่ห้าคุณต้องตรวจสอบคลาสของตัวละครด้วย หากคุณต้องการค้นหาตัวอักษร UTF-8 ตัวที่หนึ่งในสตริงคุณต้องใช้การวนซ้ำนี้เป็นล้านครั้ง! หากคุณรู้ว่าคุณจะต้องทำงานกับดัชนีบ่อยครั้งคุณสามารถสำรวจสตริงหนึ่งครั้งและสร้างดัชนีของมันหรือคุณสามารถแปลงเป็นการเข้ารหัสแบบความกว้างคงที่เช่น UCS-4 การค้นหาตัวอักษร UCS-4 ที่เป็นล้านในสตริงเป็นเพียงเรื่องของการเพิ่มสี่ล้านไปยังที่อยู่ของอาเรย์

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

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

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


7

เหตุใดประเภทหนึ่งมีขนาดเดียวเท่านั้นที่เกี่ยวข้องเมื่อพื้นที่ที่ต้องการแสดงค่าอาจน้อยกว่าขนาดนั้น

ส่วนใหญ่เป็นเพราะความต้องการการจัดตำแหน่ง

ตามพื้นฐาน. Align / 1 :

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

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

หากห้องไม่เรียงกันโครงกระดูกอาคารจะไม่ได้รับการจัดโครงสร้างอย่างดี


7

มันสามารถน้อย พิจารณาฟังก์ชั่น:

int foo()
{
    int bar = 1;
    int baz = 42;
    return bar+baz;
}

มันจะรวบรวมรหัสการประกอบ (g ++, x64, ปล้นรายละเอียด)

$43, %eax
ret

ที่นี่barและbazจบลงด้วยการใช้ศูนย์ไบต์เพื่อเป็นตัวแทน


5

เหตุใดฉันจึงไม่ได้ครอบครองหน่วยความจำเพียง 1 ไบต์

เพราะคุณบอกให้ใช้มันมากขนาดนั้น เมื่อใช้unsigned intงานมาตรฐานบางอย่างจะบอกว่าจะใช้ 4 ไบต์และช่วงที่มีให้จะอยู่ระหว่าง 0 ถึง 4,294,967,295 หากคุณจะใช้unsigned charแทนคุณอาจจะใช้เพียง 1 ไบต์ที่คุณกำลังมองหา (ขึ้นอยู่กับมาตรฐานและปกติ C ++ จะใช้มาตรฐานเหล่านี้)

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

สำหรับเหตุผลที่เราใช้ 8 บิตต่อไบต์คุณสามารถดูสิ่งนี้: ประวัติของเหตุใดไบต์ถึงแปดบิตคืออะไร

ในหมายเหตุด้านคุณสามารถอนุญาตให้จำนวนเต็มล้น; แต่คุณควรใช้จำนวนเต็มที่มีลายเซ็นรัฐมาตรฐาน C \ C ++ ที่ล้นจำนวนเต็มส่งผลในพฤติกรรมที่ไม่ได้กำหนด ล้นจำนวนเต็ม


5

บางสิ่งง่ายๆที่คำตอบส่วนใหญ่ดูเหมือนจะพลาด:

เพราะมันเหมาะกับเป้าหมายการออกแบบของ C ++

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

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

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


5

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

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

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

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


3

คอมไพเลอร์ได้รับอนุญาตให้ทำการเปลี่ยนแปลงรหัสของคุณได้มากมายตราบใดที่ยังใช้งานได้อยู่ (กฎ "ตามสภาพ")

เป็นไปได้ที่จะใช้คำสั่งการย้ายตัวอักษร 8 บิตแทนการใช้ความยาว (32/64 บิต) ที่นานขึ้นในการย้ายแบบเต็ม intต้องย้ายเต็ม อย่างไรก็ตามคุณจะต้องมีสองคำแนะนำในการโหลดให้สมบูรณ์เนื่องจากคุณจะต้องตั้งค่าการลงทะเบียนเป็นศูนย์ก่อนที่จะทำการโหลด

มันมีประสิทธิภาพมากกว่า (อย่างน้อยตามคอมไพเลอร์หลัก) เพื่อจัดการกับค่าเป็น 32 บิต ที่จริงแล้วฉันยังไม่เห็นคอมไพเลอร์ x86 / x86_64 ที่จะทำการโหลด 8 บิตโดยไม่มีการประกอบแบบอินไลน์

อย่างไรก็ตามสิ่งต่าง ๆ เมื่อมันมาถึง 64 บิต เมื่อออกแบบส่วนขยายก่อนหน้า (จาก 16 ถึง 32 บิต) ของโปรเซสเซอร์ Intel ทำผิดพลาด นี่คือตัวแทนที่ดีของสิ่งที่พวกเขาดูเหมือน ประเด็นหลักที่นี่คือเมื่อคุณเขียนถึง AL หรือ AH สิ่งอื่น ๆ จะไม่ได้รับผลกระทบ (ยุติธรรมมากนั่นคือประเด็นและมันก็สมเหตุสมผลแล้ว) แต่มันน่าสนใจเมื่อพวกมันขยายเป็น 32 บิต ถ้าคุณเขียนบิตด้านล่าง (AL, AH หรือ AX) ไม่มีอะไรเกิดขึ้นกับ EAX 16 บิตส่วนบนซึ่งหมายความว่าถ้าคุณต้องการเลื่อนระดับ a charไปเป็น a intคุณต้องล้างหน่วยความจำก่อน แต่คุณไม่มีทางที่จะ ที่จริงแล้วใช้เพียง 16 บิตแรกเท่านั้นทำให้ "คุณสมบัติ" นี้เจ็บปวดมากกว่าอะไร

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

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


การแก้ไข: เป็นถ้า นอกจากนี้ฉันไม่เห็นด้วยว่าถ้าโหลด / ร้านค้าที่สั้นกว่านั้นสามารถเพิ่มไบต์อื่นให้ใช้งานได้ซึ่งน่าจะเป็นสิ่งมหัศจรรย์ของ OP: ไม่ใช่แค่หลีกเลี่ยงการสัมผัสหน่วยความจำที่ไม่ต้องการโดยค่าปัจจุบัน แต่สามารถบอกได้ว่ามีกี่ไบต์ในการอ่านและสามารถเปลี่ยนแรมทั้งหมดในขณะทำงานดังนั้นจึงมีความคิดแปลก ๆ เกี่ยวกับการใช้พื้นที่ - ประสิทธิภาพ (ไม่ต้องคำนึงถึงต้นทุนที่สูงมาก!) ได้พบ ... เพียงแค่มีคำแนะนำที่ต่ำกว่า 'แก้' ที่ สิ่งที่ซีพียู / ระบบปฏิบัติการจำเป็นต้องทำนั้นมีความซับซ้อนจนตอบคำถามที่ชัดเจนที่สุด IMO
underscore_d

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