เหตุใด C สตริงตัวอักษรอ่านอย่างเดียว?


29

ข้อได้เปรียบของสตริงตัวอักษรที่อ่านได้อย่างเดียวคืออะไร (-ies / -ied):

  1. อีกวิธีในการยิงตัวเองที่เท้า

    char *foo = "bar";
    foo[0] = 'd'; /* SEGFAULT */
  2. ไม่สามารถเริ่มต้นอาร์เรย์ของคำอ่าน - เขียนได้อย่างหรูหราในหนึ่งบรรทัด:

    char *foo[] = { "bar", "baz", "running out of traditional placeholder names" };
    foo[1][2] = 'n'; /* SEGFAULT */ 
  3. ภาษาที่ซับซ้อนนั้นเอง

    char *foo = "bar";
    char var[] = "baz";
    some_func(foo); /* VERY DANGEROUS! */
    some_func(var); /* LESS DANGEROUS! */

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

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

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

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


12
ฉันคิดว่าคุณได้ดูว่าตัวอักษรสตริงดูในโค้ดที่คอมไพล์แล้วหรือยัง?

2
ดูแอสเซมบลีที่ลิงค์ที่ฉันให้ไว้มี มันอยู่ที่นั่น

8
ตัวอย่าง "moregex" ของคุณไม่สามารถใช้งานได้เนื่องจากการยกเลิกแบบ null
dan04

4
คุณไม่ต้องการเขียนทับค่าคงที่เพราะจะเปลี่ยนค่าของพวกเขา ครั้งต่อไปที่คุณต้องการใช้ค่าคงที่เดียวกันมันจะแตกต่างกัน คอมไพเลอร์ / รันไทม์ต้องจัดหาค่าคงที่จากที่ใดที่หนึ่งและไม่ควรอนุญาตให้แก้ไข
Erik Eidt

1
'ดังนั้นตัวอักษรสตริงจะถูกเก็บไว้ในหน่วยความจำของโปรแกรมไม่ใช่ RAM และบัฟเฟอร์ล้นจะส่งผลให้เกิดความเสียหายของโปรแกรมเอง' อิมเมจโปรแกรมอยู่ใน RAM ด้วย เพื่อความแม่นยำตัวอักษรสตริงจะถูกเก็บไว้ในส่วนเดียวกันของ RAM ที่ใช้ในการจัดเก็บภาพของโปรแกรม และใช่การเขียนทับสตริงอาจทำให้โปรแกรมเสียหาย ย้อนกลับไปในสมัยของ MS-DOS และ CP / M ไม่มีการป้องกันหน่วยความจำคุณสามารถทำสิ่งนี้และมักจะทำให้เกิดปัญหาร้ายแรง ไวรัสพีซีตัวแรกจะใช้ลูกเล่นเช่นนั้นเพื่อปรับเปลี่ยนโปรแกรมของคุณดังนั้นจึงฟอร์แมตฮาร์ดไดรฟ์ของคุณเมื่อคุณพยายามเรียกใช้
Charles E. Grant

คำตอบ:


40

ในอดีต (บางทีโดยการเขียนบางส่วนของมัน) มันเป็นสิ่งที่ตรงกันข้าม ในคอมพิวเตอร์เครื่องแรกของต้นปี 1970 (อาจเป็นPDP-11 ) ที่ใช้ตัวอ่อนต้นแบบ C (บางทีBCPL ) ไม่มีMMUและไม่มีการป้องกันหน่วยความจำ (ซึ่งมีอยู่บนเมนเฟรมของIBM / 360 ที่เก่าที่สุด) ดังนั้นหน่วยความจำทุกไบต์ (รวมถึงการจัดการสตริงตัวอักษรหรือรหัสเครื่อง) อาจถูกเขียนทับโดยโปรแกรมที่ผิดพลาด (ลองนึกภาพโปรแกรมที่เปลี่ยนบางอย่าง%ไป/ในprintf (3)สตริงรูปแบบ) ดังนั้นสายอักขระและค่าคงที่จึงเขียนได้

เมื่อเป็นวัยรุ่นในปี 1975 ฉันเขียนโค้ดในพิพิธภัณฑ์ Palais de la Découverteในกรุงปารีสโดยใช้คอมพิวเตอร์ยุค 60 ปีโดยไม่มีการป้องกันหน่วยความจำ: IBM / 1620มีหน่วยความจำหลักเท่านั้น - ซึ่งคุณสามารถเริ่มต้นผ่านแป้นพิมพ์ได้หลายสิบ ของตัวเลขเพื่ออ่านโปรแกรมเริ่มต้นบนเทปเจาะ; CAB / 500มีหน่วยความจำดรัมแม่เหล็ก; คุณสามารถปิดการเขียนแทร็กผ่านสวิตช์เชิงกลใกล้กับดรัม

ต่อมาคอมพิวเตอร์ได้รับรูปแบบของหน่วยความจำการจัดการหน่วย (MMU) ที่มีการป้องกันหน่วยความจำบางส่วน มีอุปกรณ์ที่ห้ามไม่ให้ซีพียูเขียนทับหน่วยความจำบางชนิด ดังนั้นหน่วยความจำบางเซ็กเมนต์สะดุดตาเซ็กเมนต์รหัส ( เซ็กเมนต์ aka .text) กลายเป็นแบบอ่านอย่างเดียว (ยกเว้นโดยระบบปฏิบัติการที่โหลดจากดิสก์) มันเป็นเรื่องธรรมดาสำหรับคอมไพเลอร์และตัวเชื่อมโยงที่จะนำสตริงตัวอักษรในส่วนรหัสนั้นและสตริงตัวอักษรกลายเป็นอ่านอย่างเดียว เมื่อโปรแกรมของคุณพยายามที่จะเขียนทับพวกเขาก็ไม่เป็นผลดีเป็นพฤติกรรมที่ไม่ได้กำหนด และการมีเซ็กเมนต์โค้ดแบบอ่านอย่างเดียวในหน่วยความจำเสมือนให้ประโยชน์ที่สำคัญ: กระบวนการหลายอย่างที่รันโปรแกรมเดียวกันใช้RAMเดียวกัน( หน่วยความจำกายภาพ)หน้า) สำหรับส่วนรหัสนั้น (ดูที่การMAP_SHAREDตั้งค่าสถานะสำหรับmmap (2)บน Linux)

วันนี้ไมโครคอนโทรลเลอร์ราคาถูกมีหน่วยความจำแบบอ่านอย่างเดียว (เช่นแฟลชหรือ ROM) และเก็บรหัสไว้ (และสตริงตัวอักษรและค่าคงที่อื่น ๆ ) ที่นั่น และไมโครโปรเซสเซอร์จริง (เช่นในแท็บเล็ตแล็ปท็อปหรือเดสก์ทอป) มีหน่วยจัดการหน่วยความจำที่มีความซับซ้อนและแคชเครื่องจักรที่ใช้สำหรับหน่วยความจำเสมือนและเพจ ดังนั้นส่วนของรหัสของปฏิบัติการโปรแกรม (เช่นในเอลฟ์ ) เป็นหน่วยความจำที่แมปเป็นแบบอ่านอย่างเดียวที่สามารถแชร์และส่วนปฏิบัติการ (โดยmmap (2)หรือexecve (2)บน Linux; BTW คุณสามารถให้คำแนะนำแก่LDเพื่อรับเซ็กเมนต์รหัสที่เขียนได้หากคุณต้องการ) การเขียนหรือการเหยียดหยามมันโดยทั่วไปเป็นความผิดส่วน

ดังนั้นมาตรฐาน C คือบาร็อค: ถูกกฎหมาย (สำหรับเหตุผลทางประวัติศาสตร์เท่านั้น) สตริงตัวอักษรไม่ใช่const char[]อาร์เรย์ แต่มีเฉพาะchar[]อาร์เรย์ที่ถูกห้ามไม่ให้เขียนทับ

BTW บางภาษาปัจจุบันอนุญาตให้มีการเขียนตัวอักษรสตริง (แม้ Ocaml ซึ่งในอดีต - และไม่ดี - มีสตริงตัวอักษรที่เขียนได้เปลี่ยนพฤติกรรมนั้นเมื่อเร็ว ๆ นี้ใน 4.02 และตอนนี้มีสตริงแบบอ่านอย่างเดียว)

คอมไพเลอร์ C ปัจจุบันสามารถเพิ่มประสิทธิภาพและมี"ions"และ"expressions"แบ่งปัน 5 ไบต์สุดท้ายของพวกเขา (รวมถึงการยกเลิก null null)

ลองรวบรวมรหัส C ของคุณในไฟล์foo.cด้วยgcc -O -fverbose-asm -S foo.cและดูข้างในไฟล์แอสเซมเบลอร์ที่สร้างfoo.sโดยGCC

ในที่สุดความหมายของ C นั้นซับซ้อนเพียงพอ (อ่านเพิ่มเติมเกี่ยวกับCompCert & Frama-Cซึ่งพยายามจับภาพ) และการเพิ่มสตริงตัวอักษรคงที่ที่เขียนได้จะทำให้มีความลับมากขึ้นในขณะที่ทำให้โปรแกรมอ่อนลงและปลอดภัยน้อยลง พฤติกรรมที่กำหนดไว้) ดังนั้นจึงไม่น่าเป็นไปได้มากที่มาตรฐาน C ในอนาคตจะยอมรับสตริงตัวอักษรที่เขียนได้ บางทีในทางตรงกันข้ามพวกเขาจะทำให้พวกเขาconst char[]อาร์เรย์ตามที่ควรจะเป็นทางศีลธรรม

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

ใน Fortran77 วันเก่าบน IBM / 7094 ข้อผิดพลาดอาจเปลี่ยนค่าคงที่: ถ้าคุณCALL FOO(1)และถ้าFOOเกิดขึ้นเพื่อแก้ไขอาร์กิวเมนต์ที่ส่งผ่านโดยอ้างอิงถึง 2 การใช้งานอาจมีการเปลี่ยนแปลงเกิดขึ้นเป็น 1 เป็น 2 และนั่นเป็นเรื่องจริง ข้อผิดพลาดที่ค่อนข้างหายาก


เป็นการป้องกันสายอักขระเป็นค่าคงที่หรือไม่ แม้ว่าจะไม่ได้กำหนดไว้constในมาตรฐาน ( stackoverflow.com/questions/2245664/… )?
Marius Macijauskas

คุณแน่ใจหรือว่าคอมพิวเตอร์เครื่องแรกไม่มีหน่วยความจำแบบอ่านอย่างเดียว? นั่นมันราคาถูกกว่า ram ไม่มากใช่ไหม การใส่มันไว้ในหน่วยความจำ RO ไม่ทำให้เกิด UB ในการพยายามปรับเปลี่ยนพวกเขาอย่างผิดพลาด แต่พึ่งพา OP ไม่ทำเช่นนั้นและเขาละเมิดความไว้วางใจที่ ดูตัวอย่าง Fortran โปรแกรมที่ทุกตัวอักษร1s จู่ ๆ ก็ทำตัวเหมือน2และความสนุกสนานเช่น ...
Deduplicator

1
ในฐานะวัยรุ่นในพิพิธภัณฑ์ฉันเขียนโค้ดในปี 1975 กับคอมพิวเตอร์ IBM / 1620 และ CAB500 เก่า ไม่มี ROM ใด ๆ : IBM / 1620 มีหน่วยความจำหลักและ CAB500 มีดรัมแม่เหล็ก (บางแทร็กสามารถปิดใช้งานได้โดยสวิตช์เชิงกล)
Basile Starynkevitch

2
ควรค่าแก่การชี้ให้เห็นด้วย: การใส่ตัวอักษรในส่วนรหัสหมายความว่าพวกเขาสามารถใช้ร่วมกันในหลาย ๆ สำเนาของโปรแกรมเพราะการเริ่มต้นเกิดขึ้นในเวลารวบรวมมากกว่าเวลาทำงาน
Blrfl

@Dupuplicator ดีฉันเห็นเครื่องที่ใช้งานตัวแปรพื้นฐานซึ่งอนุญาตให้คุณเปลี่ยนค่าคงที่จำนวนเต็ม (ฉันไม่แน่ใจว่าคุณต้องการหลอกให้ทำเช่นนั้นหรือไม่เช่นผ่านอาร์กิวเมนต์ "byref" หรือหากlet 2 = 3ทำงานได้ง่าย) สิ่งนี้ส่งผลให้ความสนุกมากมาย (ในคำจำกัดความของป้อมปราการคนแคระของคำ) แน่นอน ฉันไม่ทราบเลยว่าล่ามได้รับการออกแบบให้ใช้ได้อย่างไร แต่เป็นเช่นนั้น
Luaan

2

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

ปัญหาที่คล้ายกันจะเกิดขึ้นใน FORTRAN ก่อนการประดิษฐ์ C. ข้อโต้แย้งจะถูกส่งผ่านตามที่อยู่เสมอแทนที่จะเป็นตามค่า รูทีนเพื่อเพิ่มตัวเลขสองตัวจึงเท่ากับ:

float sum(float *f1, float *f2) { return *f1 + *f2; }

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

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