อะไรคือเหตุผลสำหรับ fread / fwrite ที่มีขนาดและนับเป็นอาร์กิวเมนต์?


96

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

จากFREAD (3) :

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

ฟังก์ชัน fwrite () เขียนองค์ประกอบ nmemb ของข้อมูลแต่ละขนาดไบต์ยาวไปยังสตรีมที่สตรีมชี้ไปโดยรับข้อมูลจากตำแหน่งที่กำหนดโดย ptr

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


11
นี่เป็นคำถามที่ดี ฉันสงสัยอยู่เสมอ
Johannes Schaub - litb

1
โปรดดูหัวข้อนี้: stackoverflow.com/questions/8589425/how-does-fread-really-work
Franken

คำตอบ:


22

ก็ขึ้นอยู่กับวิธีการfreadจะดำเนินการ

ข้อกำหนด Single UNIX กล่าว

สำหรับแต่ละออบเจ็กต์การเรียกขนาดจะต้องทำกับฟังก์ชัน fgetc () และผลลัพธ์ที่จัดเก็บตามลำดับที่อ่านในอาร์เรย์ของถ่านที่ไม่ได้ลงนามซึ่งซ้อนทับวัตถุทั้งหมด

fgetcยังมีหมายเหตุนี้:

เนื่องจาก fgetc () ทำงานบนไบต์การอ่านอักขระที่ประกอบด้วยหลายไบต์ (หรือ "อักขระแบบหลายไบต์") อาจต้องใช้การเรียก fgetc () หลายครั้ง

แน่นอนว่าสิ่งนี้เกิดขึ้นก่อนการเข้ารหัสอักขระแบบไบต์ตัวแปรแฟนซีเช่น UTF-8

SUS ตั้งข้อสังเกตว่าสิ่งนี้นำมาจากเอกสาร ISO C


72

ความแตกต่างของ fread (buf, 1000, 1, stream) และ fread (buf, 1, 1000, stream) คือในกรณีแรกคุณจะได้รับเพียงชิ้นเดียวที่มีขนาด 1,000 ไบต์หรือ nuthin หากไฟล์มีขนาดเล็กกว่าและอยู่ใน กรณีที่สองคุณได้รับทุกอย่างในไฟล์น้อยกว่าและมากถึง 1,000 ไบต์


4
แม้ว่าจะเป็นความจริง แต่นั่นบอกเล่าเรื่องราวเพียงส่วนเล็ก ๆ เท่านั้น มันจะดีกว่าถ้าเปรียบเทียบบางสิ่งที่อ่านเช่นอาร์เรย์ของค่า int หรือโครงสร้างอาร์เรย์
Jonathan Leffler

3
สิ่งนี้จะให้คำตอบที่ดีหากเหตุผลเสร็จสมบูรณ์
Matt Joiner

14

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

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


1
ฉันดีใจที่มีคนนำเรื่องนี้ขึ้นมา ฉันทำงานหลายอย่างกับข้อมูลจำเพาะของระบบไฟล์และ FTP และบันทึก / เพจและแนวคิดการบล็อกอื่น ๆ ได้รับการสนับสนุนอย่างแน่นหนาแม้ว่าจะไม่มีใครใช้ส่วนของข้อกำหนดเหล่านั้นอีกต่อไป
Matt Joiner

9

ขอฉันแก้ไขฟังก์ชันเหล่านั้น:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

สำหรับเหตุผลของพารามิเตอร์ถึงfread()/ fwrite()ฉันทำสำเนา K&R หายไปนานแล้วดังนั้นฉันจึงเดาได้เท่านั้น ฉันคิดว่าคำตอบที่เป็นไปได้คือ Kernighan และ Ritchie อาจคิดเพียงว่าการแสดงไบนารี I / O จะทำได้อย่างเป็นธรรมชาติที่สุดในอาร์เรย์ของวัตถุ นอกจากนี้พวกเขาอาจคิดว่า block I / O จะใช้งานได้เร็วขึ้น / ง่ายขึ้นหรืออะไรก็ตามในบางสถาปัตยกรรม

แม้ว่ามาตรฐาน C จะระบุfread()และfwrite()นำไปใช้ในแง่ของfgetc()และfputc()โปรดจำไว้ว่ามาตรฐานนั้นมีมานานแล้วหลังจากที่ C ถูกกำหนดโดย K&R และสิ่งที่ระบุในมาตรฐานอาจไม่ได้อยู่ในแนวคิดของนักออกแบบดั้งเดิม แม้เป็นไปได้ว่าสิ่งที่พูดใน "ภาษาโปรแกรมซี" ของ K & R อาจไม่เหมือนกับตอนที่ออกแบบภาษาครั้งแรก

สุดท้ายนี้คือสิ่งที่ PJ Plauger กล่าวถึงfread()ใน "The Standard C Library":

หากsizeอาร์กิวเมนต์ (ที่สอง) มากกว่าหนึ่งคุณจะไม่สามารถระบุได้ว่าฟังก์ชันนั้นอ่านsize - 1อักขระเพิ่มเติมนอกเหนือจากที่รายงานหรือไม่ ตามกฎแล้วคุณควรเรียกใช้ฟังก์ชันfread(buf, 1, size * n, stream);แทน fread(buf, size, n, stream);

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


17
อันที่จริงฉันมักจะชอบทำในทางอื่น: fread(buf, size*n, 1, stream);หากการอ่านที่ไม่สมบูรณ์เป็นเงื่อนไขข้อผิดพลาดการจัดเรียงfreadให้ส่งคืน 0 หรือ 1 นั้นง่ายกว่าแทนที่จะอ่านจำนวนไบต์ จากนั้นคุณสามารถทำสิ่งต่างๆเช่นif (!fread(...))แทนที่จะต้องเปรียบเทียบผลลัพธ์กับจำนวนไบต์ที่ร้องขอ (ซึ่งต้องใช้รหัส C เพิ่มเติมและรหัสเครื่องเพิ่มเติม)
R .. GitHub STOP HELPING ICE

1
@R .. อย่าลืมตรวจสอบขนาด * count! = 0 นอกเหนือจาก! fread (... ) หาก size * count == 0 คุณจะได้รับค่าผลตอบแทนเป็นศูนย์จากการอ่านสำเร็จ (ของศูนย์ไบต์), feof () และ ferror () จะไม่ถูกตั้งค่าและ errno จะเป็นสิ่งที่ไร้สาระเช่น ENOENT หรือแย่กว่านั้น สิ่งที่ทำให้เข้าใจผิด (และอาจถึงขั้นวิกฤต) เช่น EAGAIN - สับสนมากโดยเฉพาะอย่างยิ่งเนื่องจากโดยทั่วไปแล้วไม่มีเอกสารใดที่ทำให้ gotcha นี้ตะโกนใส่คุณ
Pegasus Epsilon

3

เป็นไปได้ว่าจะกลับไปใช้วิธีการใช้งานไฟล์ I / O (ย้อนกลับไปในวันนี้) การเขียน / อ่านไฟล์ในบล็อกอาจเร็วกว่านั้นในการเขียนทุกอย่างพร้อมกัน


ไม่จริง. ข้อกำหนด C สำหรับ fwrite บันทึกว่าทำการเรียก fputc ซ้ำ ๆ : opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord

1

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

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


@PegasusEpsilon: ถ้าเช่นโปรแกรมทำfread(buffer, 10000, 2, stdin)และผู้ใช้พิมพ์ newline-ctrl-D หลังจากพิมพ์ 18,000 ไบต์มันจะดีถ้าฟังก์ชันสามารถส่งคืน 10,000 ไบต์แรกในขณะที่เหลืออีก 8,000 ที่รอดำเนินการสำหรับคำขออ่านขนาดเล็กในอนาคต แต่จะมี การใช้งานใด ๆ ที่จะเกิดขึ้น? 8,000 ไบต์จะถูกเก็บไว้ที่ไหนเพื่อรอการร้องขอในอนาคต
supercat

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

... ต่อ ... สิ่งที่ดีที่สุดที่คุณทำได้คืออาจเติมบัฟเฟอร์การอ่านของคุณด้วย null ก่อน fread และตรวจสอบบันทึกหลังจากที่ fread () บอกว่าเสร็จสิ้นสำหรับไบต์ที่ไม่ใช่ null ใด ๆ ไม่ได้ช่วยคุณเป็นพิเศษเมื่อระเบียนของคุณอาจมีค่าว่าง แต่ถ้าคุณจะใช้sizeมากกว่า 1 ก็เช่นกัน ... สำหรับบันทึกอาจมี ioctls หรือเรื่องไร้สาระอื่น ๆ ที่คุณสามารถนำไปใช้กับสตรีมได้ ทำตัวแตกต่างฉันไม่ได้เจาะลึกขนาดนั้น
Pegasus Epsilon

นอกจากนี้ฉันยังลบความคิดเห็นก่อนหน้านี้เนื่องจากความไม่ถูกต้อง โอ้ดี.
Pegasus Epsilon

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

0

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

พิจารณาสิ่งนี้:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

หาก fwrite ยอมรับจำนวนไบต์คุณสามารถเขียนสิ่งต่อไปนี้:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

แต่มันก็ไร้ประสิทธิภาพ คุณจะมีการเรียกระบบ sizeof (int) มากขึ้นหลายเท่า

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

ในบางระบบ (เนื่องจากการจัดตำแหน่ง) คุณไม่สามารถเข้าถึงหนึ่งไบต์ของจำนวนเต็มโดยไม่ต้องสร้างสำเนาและการขยับ

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