ฟรีจะรู้ได้อย่างไรว่าฟรีมากแค่ไหน?


384

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


คำถามที่คล้ายกัน: stackoverflow.com/questions/851958/... ( แต่ผมว่ามันไม่ได้ค่อนข้างซ้ำกัน)
จอห์นคาร์เตอร์

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

โพสต์นี้อธิบายได้ดี: stackoverflow.com/questions/1957099/…
Zeeshan Mahmood

คำตอบ:


348

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

เมื่อคุณโทรfree()มันจะดูข้อมูลเพิ่มเติมเพื่อดูว่าบล็อกใหญ่แค่ไหน


44
FYI เช่น BSD malloc_size()ต้องเข้าถึงขนาดบล็อกได้อย่างแม่นยำจากmalloc()ตัวชี้ ed แต่ไม่มีวิธีการพกพาที่เชื่อถือได้
laalto

50
ฉันคิดว่ามันสำคัญที่จะบอกว่าบล็อกข้อมูลพิเศษนี้ตั้งอยู่หน้าตัวชี้ที่ส่งคืน
Georg Schölly

39
@gs นั่นขึ้นอยู่กับการใช้งาน แต่ใช่ว่ามันเป็นเรื่องปกติ
Falaina

31
คุณสามารถจินตนาการความสยองขวัญได้หรือไม่หากfree()ต้องการให้โปรแกรมเมอร์รายงานอย่างแม่นยำว่าmalloc()บล็อกใหญ่แค่ไหน? หน่วยความจำรั่วไหลไม่ดีพอเหมือนเดิม
MusiGenesis

35
เหตุใดจึงมีข้อมูลนั้นmalloc()และfree()แต่คุณต้องเก็บขนาดของอาร์เรย์ ทำไมพวกเขาถึงไม่ทำแบบนี้blockSize(ptr)ถ้าพวกเขาเก็บข้อมูลล่ะ?
corsiKa

144

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

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

  • ส่วนหัวขนาด 16- ไบต์ที่มีขนาดเครื่องหมายพิเศษเช็คซัมพอยน์เตอร์ไปยังบล็อกถัดไป / ก่อนหน้าและอื่น ๆ
  • พื้นที่ข้อมูล 32 ไบต์ (20 ไบต์ของคุณมีขนาดเท่ากับ 16)

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

 ____ The allocated block ____
/                             \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
          ^
          |
          +-- The address you are given

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

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

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


(a)ฉันเขียนการใช้งานของmallocในระบบฝังตัวที่คุณมี 128 ไบต์ไม่ว่าคุณจะถามอะไร (นั่นคือขนาดของโครงสร้างที่ใหญ่ที่สุดในระบบ) โดยสมมติว่าคุณขอ 128 ไบต์หรือน้อยกว่า พบกับค่าตอบแทน NULL) บิตมาสก์ที่ง่ายมาก (เช่นไม่ใช่ในบรรทัด) ถูกใช้เพื่อตัดสินใจว่ามีการจัดสรรก้อนขนาด 128 ไบต์หรือไม่

คนอื่น ๆ ที่ฉันพัฒนาขึ้นนั้นมีกลุ่มที่ต่างกันสำหรับ 16-byte chunks, 64-bytes chunks, 256-byte chunks และ 1K chunks อีกครั้งโดยใช้ bit-mask เพื่อตัดสินใจว่าจะใช้บล็อกหรือบล็อกใด

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


@paxdiablo นั่นหมายความว่า malloc ไม่ได้จัดสรรบล็อกหน่วยความจำต่อเนื่องกันใช่ไหม
user10678

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

คำถามที่เกี่ยวข้อง: ทำไมไม่มี malloc / free รูปแบบใดที่คุณระบุขนาดเมื่อทำการพ้นและดังนั้นจึงไม่จำเป็นต้องจัดเก็บขนาด
user253751

@ user253751 เพราะอย่างนั้นอีกสิ่งหนึ่งของ theer คุณจะต้องติดตามตัวชี้และตัวชี้ขึ้นไป มันทั้งที่ไม่จำเป็นและอันตราย: void *x = malloc(200); free(x, 500);จะไม่จบ :-) ไม่ว่าในกรณีใดก็ตามประสิทธิภาพขนาดบัฟเฟอร์ที่แท้จริงอาจใหญ่กว่าเดิม (คุณไม่สามารถพึ่งพาสิ่งนี้ได้)
paxdiablo

@paxdiablo นอกจากนี้ยังหลีกเลี่ยงการสูญเสียความทรงจำในการเก็บขนาด
user253751

47

จากcomp.lang.cรายการคำถามที่พบบ่อย: ฟรีจะรู้จำนวนไบต์ฟรีได้อย่างไร

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


2
นี่คือคำตอบที่ไม่ใช่ คำถามคือตรงนี้: ทำไมสามารถค้นหาขนาดของบล็อกได้อย่างอิสระ แต่ยังไม่มีฟังก์ชั่นสำหรับโปรแกรมเมอร์ที่ทำเช่นนั้น?
Bananach

นี่เป็นรายละเอียดการใช้งานจริงสำหรับ malloc api และไม่มี API ที่จะรับข้อมูลนี้กลับมาในแบบมาตรฐาน (ตามความรู้ของฉัน) "System" freeบันทึกมันและการใช้งานที่เกี่ยวกับการ บางทีคำตอบอาจไม่ทำให้คุณพึงพอใจ แต่ฉันไม่คิดว่าคุณจะได้รับข้อมูลที่ใช้งานได้โดยทั่วไป :-)
jdehaan

6

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


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

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

การพยายามfreeชี้ใด ๆ ที่มีค่าแตกต่างจากที่ส่งคืนโดยmallocs ก่อนหน้านี้และเนื่องจากยังไม่ได้เห็นเป็นข้อผิดพลาด mallocมันไม่ได้เป็นไปได้ที่จะพื้นที่หน่วยความจำฟรีบางส่วนกลับมาจาก


ฉันเปลี่ยนค่าของตัวชี้ที่ส่งคืนโดยการเรียก malloc และฉันก็ปล่อยมันโดยไม่มีข้อผิดพลาด ทำไม? ดูที่นี่: stackoverflow.com/questions/42618390/…
smwikipedia

4

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


3

malloc()และfree()ขึ้นอยู่กับระบบ / ผู้แปลจึงยากที่จะให้คำตอบเฉพาะ

ข้อมูลเพิ่มเติมเกี่ยวกับคำถามอื่นนี้


2
มันขึ้นอยู่กับไลบรารีจริงๆ (โดยทั่วไปคือไลบรารี C ซึ่งมักเชื่อมโยงกับระบบปฏิบัติการอย่างใกล้ชิด) สำหรับคอมไพเลอร์พวกมันแค่ทำหน้าที่
Donal Fellows

2

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

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


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

2

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

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

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

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


1

ในการตอบคำถามครึ่งหลังของคุณ: ใช่คุณสามารถทำได้และรูปแบบที่พบบ่อยใน C คือ:

typedef struct {
    size_t numElements
    int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;

#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;

นั่นเป็นเทคนิคที่แตกต่างอย่างสิ้นเชิงกับหนึ่งใน BSD malloc ที่ใช้สำหรับวัตถุขนาดเล็ก (แม้ว่ามันจะเป็นเทคนิคที่ดีอย่างสมบูรณ์แบบสำหรับการสร้างอาร์เรย์สไตล์ Pascal)
Pete Kirkham

0

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


0

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

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