เมื่อออกแบบภาษาการเขียนโปรแกรมของตัวเองมันจะเหมาะสมที่จะเขียนตัวแปลงที่ใช้ซอร์สโค้ดและแปลงเป็นรหัส C หรือ C ++ เพื่อให้ฉันสามารถใช้คอมไพเลอร์ที่มีอยู่เช่น gcc เพื่อจบด้วยรหัสเครื่อง? มีโครงการที่ใช้วิธีนี้หรือไม่?
เมื่อออกแบบภาษาการเขียนโปรแกรมของตัวเองมันจะเหมาะสมที่จะเขียนตัวแปลงที่ใช้ซอร์สโค้ดและแปลงเป็นรหัส C หรือ C ++ เพื่อให้ฉันสามารถใช้คอมไพเลอร์ที่มีอยู่เช่น gcc เพื่อจบด้วยรหัสเครื่อง? มีโครงการที่ใช้วิธีนี้หรือไม่?
คำตอบ:
การเปลี่ยนรหัส C เป็นนิสัยที่ยอมรับกันเป็นอย่างดี C ดั้งเดิมพร้อมคลาส (และการใช้งาน C ++ ก่อนหน้านี้แล้วเรียกว่าCfront ) ทำเช่นนั้นได้สำเร็จ หลายคนใช้งานของเสียงกระเพื่อมหรือโครงการที่จะทำเช่นโครงการไก่ , Scheme48 , Bigloo บางคนแปลเปิดฉากไปที่ C และโมสาร์ทบางรุ่นก็ทำเช่นกัน(และมีความพยายามรวบรวมOcaml bytecode ถึง C ) ระบบ CAIAปัญญาประดิษฐ์ของ J.Pitrat ยังทำการบูตและสร้างรหัส C ทั้งหมด Valaยังแปลเป็น C สำหรับรหัสที่เกี่ยวข้องกับ GTK หนังสือของ Queinnec เสียงกระเพื่อมเป็นชิ้นเล็กชิ้นน้อย มีบางบทเกี่ยวกับการแปลเป็น C.
หนึ่งในประเด็นที่เมื่อแปลไปที่ C คือสายหาง recursive มาตรฐาน C ไม่รับประกันว่า C คอมไพเลอร์แปลได้อย่างถูกต้อง (เป็น "การกระโดดด้วยการโต้แย้ง" คือโดยไม่กินการเรียกสแต็ก) แม้ว่าในบางกรณี GCC รุ่นล่าสุด (หรือ Clang / LLVM) จะทำการปรับให้เหมาะสมที่สุด .
ปัญหาก็คือการเก็บขยะ การใช้งานหลายอย่างเพียงใช้ตัวเก็บขยะแบบอนุรักษ์ Boehm (ซึ่งเป็นมิตรกับ C ... ) หากคุณต้องการเก็บรวบรวมรหัส (เช่นการใช้งาน Lisp หลายอย่างเช่น SBCL) ซึ่งอาจเป็นฝันร้าย (คุณต้องการวางdlclose
บน Posix)
แต่ปัญหาก็คือการจัดการกับชั้นแรกตและโทร / ซีซี แต่เทคนิคที่ฉลาดนั้นเป็นไปได้ (ดูภายใน Chicken Scheme) การเข้าถึง call-stack อาจต้องใช้ลูกเล่นมากมาย (แต่ดูที่GNU backtraceฯลฯ .... ) การคงอยู่มุมฉากของการต่อเนื่อง (เช่นของสแต็คหรือหัวข้อ) จะยากใน C.
การจัดการข้อยกเว้นมักจะเป็นเรื่องของการปล่อยสายที่ฉลาดให้longjmp และอื่น ๆ ...
คุณอาจต้องการสร้าง#line
คำสั่งที่เหมาะสม (ในรหัส C ของคุณที่ปล่อยออกมา) สิ่งนี้น่าเบื่อและใช้เวลาทำงานมาก (คุณจะต้องการให้สร้างgdb
โค้ดที่สามารถถอดเปลี่ยนได้ง่ายขึ้น)
ภาษาเฉพาะของโดเมนMELTของฉันlispy (เพื่อปรับแต่งหรือขยายGCC ) ถูกแปลเป็น C (จริง ๆ แล้วเป็น C ++ ที่แย่) มีตัวคัดลอกขยะ generational ของตัวเอง (คุณอาจสนใจโดยQishหรือRavenbrook MPS ) ที่จริงแล้ว generational GC นั้นง่ายกว่าในการสร้างรหัส C ขึ้นมาจากเครื่องมากกว่าในรหัส C ที่เขียนด้วยมือ (เพราะคุณจะปรับแต่งตัวสร้างรหัส C ของคุณสำหรับสิ่งกีดขวางการเขียนและเครื่องจักร GC)
ผมไม่ทราบว่าการดำเนินการใด ๆ ภาษาแปลแท้รหัส c ++ คือใช้บาง "เก็บขยะเวลารวบรวม" เทคนิคในการปล่อยรหัส c ++ ใช้มากแม่ STL และเคารพRAIIสำนวน (โปรดบอกว่าคุณรู้หรือไม่)
สิ่งที่ตลกในวันนี้คือ (บนเดสก์ท็อป Linux ปัจจุบัน) คอมไพเลอร์ C อาจเร็วพอที่จะใช้ลูปการอ่าน - Eval-loop-loopแบบอินเตอร์แอคทีฟที่แปลเป็น C: คุณจะปล่อยรหัส C (ไม่กี่ร้อยบรรทัด) ปฏิสัมพันธ์คุณจะรวบรวมมันกลายเป็นวัตถุที่ใช้ร่วมกันซึ่งคุณก็จะfork
dlopen
(MELT กำลังทำทุกอย่างที่พร้อมและมักจะเร็วพอ) ทั้งหมดนี้อาจใช้เวลาสักครู่ในสิบวินาทีและเป็นที่ยอมรับของผู้ใช้ปลายทาง
เมื่อเป็นไปได้ฉันขอแนะนำให้แปลเป็น C ไม่ใช่เฉพาะ C ++ เนื่องจากการรวบรวม C ++ ช้า
หากคุณมีการใช้ภาษาของคุณคุณยังอาจพิจารณา (แทนเปล่งรหัส C) บางJITห้องสมุดเช่นlibjit , GNU ฟ้าผ่า , asmjitหรือแม้กระทั่งLLVMหรือGCCJIT หากคุณต้องการแปลเป็น C บางครั้งคุณอาจใช้tinycc : มันรวบรวมรหัส C ที่สร้างขึ้นอย่างรวดเร็ว (แม้ในหน่วยความจำ) เพื่อทำให้รหัสเครื่องช้าลง แต่โดยทั่วไปคุณต้องการใช้ประโยชน์จากการปรับให้เหมาะสมโดยคอมไพเลอร์ C ตัวจริงเช่นGCC
หากคุณแปลภาษาของคุณเป็นภาษา C ให้แน่ใจว่าได้สร้างASTทั้งหมดของรหัส C ที่สร้างขึ้นในหน่วยความจำก่อน (สิ่งนี้จะช่วยให้ง่ายต่อการสร้างคำประกาศทั้งหมดก่อนจากนั้นจึงนิยามและฟังก์ชั่นรหัสทั้งหมด) คุณสามารถทำการปรับให้เหมาะสม / ปรับสภาพได้ด้วยวิธีนี้ นอกจากนี้คุณอาจสนใจส่วนขยาย GCCหลายรายการ(เช่น gotos ที่คำนวณ) คุณอาจต้องการหลีกเลี่ยงการสร้างฟังก์ชั่น C ขนาดใหญ่ - เช่นหนึ่งแสนบรรทัดของ C ที่สร้างขึ้น - (คุณจะแยกพวกมันออกเป็นชิ้นเล็ก ๆ ) เนื่องจากการเพิ่มประสิทธิภาพของคอมไพเลอร์ C นั้นไม่มีความสุขมากกับฟังก์ชั่น C ขนาดใหญ่มาก ทดลองgcc -O
เวลาในการรวบรวมฟังก์ชั่นขนาดใหญ่เป็นสัดส่วนกับกำลังสองของขนาดรหัสฟังก์ชั่น) ดังนั้น จำกัด ขนาดของฟังก์ชั่น C ที่คุณสร้างให้เหลือไม่กี่พันบรรทัด
โปรดสังเกตว่าทั้งคอมไพเลอร์เสียงดังกราว (ผ่านLLVM ) และGCC (ผ่านlibgccjit ) C & C ++ เสนอวิธีการที่จะปล่อยตัวแทนภายในบางส่วนที่เหมาะสำหรับคอมไพเลอร์เหล่านี้ แต่การทำเช่นนั้นอาจจะยากกว่าการเปล่งรหัส C (หรือ C ++) และเจาะจงสำหรับคอมไพเลอร์แต่ละตัว
หากการออกแบบภาษาที่จะแปลเป็น C คุณอาจต้องการกลอุบายหลายอย่าง (หรือโครงสร้าง) เพื่อสร้างการผสมผสานของ C กับภาษาของคุณ กระดาษ DSL2011 ของฉันMELT: ภาษาที่แปลโดเมนเฉพาะที่ฝังอยู่ใน GCC Compilerควรให้คำแนะนำที่เป็นประโยชน์แก่คุณ
มันสมเหตุสมผลเมื่อถึงเวลาที่จะสร้างรหัสเครื่องที่สมบูรณ์เมื่อเทียบกับความไม่สะดวกในการรวบรวม "IL" ของคุณลงในรหัสเครื่องโดยใช้ C compiler
โดยทั่วไปภาษาเฉพาะโดเมนจะถูกเขียนด้วยวิธีนี้ระบบระดับสูงมากถูกใช้เพื่อกำหนดหรืออธิบายกระบวนการที่ถูกคอมไพล์แล้วในไฟล์ที่เรียกใช้งานได้หรือ dll เวลาที่ใช้ในการสร้างแอสเซมบลีที่ทำงาน / ดีนั้นยิ่งใหญ่กว่าการสร้าง C และ C นั้นค่อนข้างใกล้โค้ดแอสเซมบลีสำหรับประสิทธิภาพดังนั้นจึงเหมาะสมที่จะสร้าง C และใช้ทักษะของนักเขียนคอมไพเลอร์ C อีกครั้ง โปรดทราบว่ามันไม่ได้เป็นเพียงแค่การคอมไพล์ แต่ปรับให้เหมาะสมด้วย - ผู้ที่เขียน gcc หรือ llvm ได้ใช้เวลาส่วนใหญ่ในการสร้างรหัสเครื่องที่ดีที่สุดมันจะงงที่จะพยายามบูรณาการการทำงานหนักทั้งหมดของพวกเขา
อาจเป็นเรื่องที่ยอมรับได้มากกว่าในการใช้แบ็กเอนด์ของ LLVM คอมไพเลอร์ที่ IIRC เป็นภาษาที่เป็นกลางดังนั้นคุณจึงสร้างคำสั่ง LLVM แทนรหัส C
การเขียนคอมไพเลอร์เพื่อสร้างรหัสเครื่องอาจไม่ยากกว่าการเขียนแบบที่สร้าง C (ในบางกรณีมันอาจจะง่ายกว่า) แต่คอมไพเลอร์ที่สร้างรหัสเครื่องจะสามารถสร้างโปรแกรมที่ทำงานได้บนแพลตฟอร์มเฉพาะซึ่ง มันถูกเขียนขึ้น; คอมไพเลอร์ที่สร้างรหัส C ในทางตรงกันข้ามอาจจะสามารถผลิตโปรแกรมสำหรับแพลตฟอร์มใด ๆ ที่ใช้ภาษาถิ่นของ C ซึ่งรหัสที่สร้างขึ้นได้รับการออกแบบเพื่อรองรับ โปรดทราบว่าในหลายกรณีอาจเป็นไปได้ที่จะเขียนรหัส C ซึ่งพกพาได้อย่างสมบูรณ์และจะทำงานได้ตามที่ต้องการโดยไม่ใช้พฤติกรรมใด ๆ ที่ไม่ได้รับประกันโดยมาตรฐาน C แต่รหัสที่อาศัยพฤติกรรมที่รับประกันแพลตฟอร์มอาจทำงานได้เร็วกว่ามาก บนแพลตฟอร์มที่ทำให้การค้ำประกันเหล่านั้นดีกว่ารหัสที่ไม่มี
ตัวอย่างเช่นสมมติว่าภาษารองรับคุณลักษณะที่ให้ผลลัพธ์UInt32
จากสี่ไบต์ต่อเนื่องของการจัดตำแหน่งตามอำเภอใจโดยUInt8[]
ตีความในรูปแบบใหญ่ ในคอมไพเลอร์บางคนสามารถเขียนรหัสเป็น:
uint32_t dat = *(__packed uint32_t*)p;
return (dat >> 24) | (dat >> 8) | ((uint32_t)dat << 8) | ((uint32_t)dat << 24));
และให้คอมไพเลอร์สร้างการดำเนินการโหลดคำตามด้วยคำสั่ง reverse-bytes-in-word อย่างไรก็ตามคอมไพเลอร์บางตัวจะไม่สนับสนุนตัวดัดแปลง __ ที่ถูกแพ็คและในกรณีที่ไม่มีมันจะสร้างโค้ดที่ไม่สามารถใช้งานได้
อีกวิธีหนึ่งสามารถเขียนรหัสเป็น:
return dat[3] | ((uint16_t)dat[2] << 8) | ((uint32_t)dat[1] << 16) | ((uint32_t)dat[0] << 24);
รหัสดังกล่าวควรทำงานบนแพลตฟอร์มใด ๆ แม้ว่าCHAR_BITS
จะไม่ใช่ 8 (โดยสมมติว่าแต่ละแหล่งข้อมูลแปดเซตสิ้นสุดลงในองค์ประกอบอาเรย์ที่แตกต่างกัน) แต่โค้ดดังกล่าวอาจไม่ทำงานเกือบจะเร็วเท่าที่อุปกรณ์พกพาจะไม่ทำงาน เวอร์ชันบนแพลตฟอร์มที่สนับสนุนอดีต
โปรดทราบว่าการพกพามักจะต้องใช้รหัสนั้นเป็นอิสระอย่างมากกับ typecasts และโครงสร้างที่คล้ายกัน ตัวอย่างเช่นโค้ดที่ต้องการคูณจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อสองตัวและให้ผลลัพธ์ที่ต่ำกว่า 32 บิตของผลลัพธ์ต้องมีความสะดวกในการพกพาเขียนเป็น:
uint32_t result = 1u*x*y;
หากปราศจากนั้น1u
คอมไพเลอร์ในระบบที่ INT_BITS อยู่ในช่วง 33-64 สามารถทำอะไรได้อย่างถูกต้องหากผลิตภัณฑ์ของ x และ y มีขนาดใหญ่กว่า 2,147,483,647 และคอมไพเลอร์บางรายมีแนวโน้มที่จะใช้ประโยชน์จากโอกาสดังกล่าว
คุณมีคำตอบที่ยอดเยี่ยมข้างต้น แต่ในความคิดเห็นคุณตอบคำถามว่า "ทำไมคุณต้องการสร้างภาษาโปรแกรมของคุณเองตั้งแต่แรก?" กับ "มันจะเป็นการเรียนรู้ส่วนใหญ่" ฉัน ' ฉันจะตอบจากมุมที่แตกต่าง
เหมาะสมที่จะเขียนตัวแปลงที่ใช้ซอร์สโค้ดและแปลงเป็นรหัส C หรือ C ++ เพื่อให้คุณสามารถใช้คอมไพเลอร์ที่มีอยู่เช่น gcc เพื่อจบลงด้วยรหัสเครื่องหากคุณสนใจเรียนรู้เกี่ยวกับคำศัพท์ไวยากรณ์และ การวิเคราะห์ความหมายมากกว่าที่คุณเรียนรู้เกี่ยวกับการสร้างรหัสและการเพิ่มประสิทธิภาพ!
การเขียนโค้ดเครื่องของคุณเองเป็นงานที่มีความสำคัญซึ่งคุณสามารถหลีกเลี่ยงได้โดยการรวบรวมเป็นรหัส C หากไม่ใช่สิ่งที่คุณสนใจเป็นหลัก!
อย่างไรก็ตามหากคุณเข้าสู่โปรแกรมการประกอบและหลงใหลในความท้าทายของการปรับรหัสให้อยู่ในระดับต่ำสุดจากนั้นลองเขียนโปรแกรมสร้างรหัสด้วยตัวคุณเองเพื่อประสบการณ์การเรียนรู้!
ขึ้นอยู่กับระบบปฏิบัติการที่คุณใช้หากคุณใช้ Windows มี Microsoft IL (ภาษาระดับกลาง) ซึ่งแปลงรหัสของคุณเป็นภาษากลางเพื่อที่จะไม่ต้องใช้เวลาในการรวบรวมเป็นรหัสเครื่อง หรือถ้าคุณใช้ลีนุกซ์มีคอมไพเลอร์แยกต่างหาก
การกลับมาที่คำถามของคุณคือเมื่อคุณออกแบบภาษาของคุณเองคุณควรมีตัวแปลภาษาหรือตัวแปลภาษาแยกต่างหากเพราะเครื่องไม่ทราบภาษาระดับสูง ควรรวบรวมรหัสของคุณเป็นรหัสเครื่องเพื่อให้เป็นประโยชน์สำหรับเครื่อง
Your code should be compiled into machine code to make it useful for machine
- หากคอมไพเลอร์ของคุณสร้างรหัส c เป็นเอาต์พุตคุณสามารถใส่รหัส c ลงในคอมไพเลอร์ ac เพื่อสร้างรหัสเครื่องใช่ไหม?