ดีกว่าที่จะใช้คำสั่ง preprocessor หรือคำสั่ง (คงที่)?


10

สมมติว่าเรามี codebase ที่ใช้สำหรับ costumers ที่แตกต่างกันมากมายและเรามี code บางอย่างที่เกี่ยวข้องกับ costumers ประเภท X ควรใช้คำสั่ง preprocessor เพื่อรวมรหัสนี้เฉพาะใน costumer ประเภท X หรือ ใช้ถ้ามีคำสั่ง? เพื่อให้ชัดเจน:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

หรือ

if(TYPE_X_COSTUMER) {
    // do some things
}

ข้อโต้แย้งที่ฉันคิดได้คือ:

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

อะไรจะดีไปกว่า - เมื่อพูดถึงฐานรหัสสำหรับลูกค้าที่แตกต่างกันจำนวนมาก


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

@delnan - รหัสนี้ใช้สำหรับแพลตฟอร์มต่าง ๆ ที่มีคอมไพเลอร์ต่างกัน และเกี่ยวกับผลกระทบ - ในบางกรณี
MByD

คุณถูกบอกให้ชอบอะไรไหม? นั่นเหมือนบังคับให้ใครบางคนกินอาหารจากเคเอฟซีในขณะที่เขาไม่ชอบไก่
rightfold

@WTP - ไม่ฉันไม่ใช่ฉันสนใจที่จะรู้มากขึ้นดังนั้นฉันจะตัดสินใจได้ดีขึ้นในอนาคต
MByD

ในตัวอย่างที่สองของคุณTYPE_X_CUSTOMERยังคงเป็นมาโครตัวประมวลผลล่วงหน้าหรือไม่
Detly

คำตอบ:


5

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

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


ที่จริงแล้วนี่เป็นระบบฝังตัว
MByD

ปัญหาคือระบบฝังตัวมักจะใช้คอมไพเลอร์โบราณ (เช่นเช่น gcc 2.95)
BЈовић

@VJo - GCC เป็นกรณีที่โชคดี :)
MByD

ลองใช้ดู หากมันมีรหัสที่ไม่ได้ใช้และมีขนาดที่พอเหมาะแล้ว
Tamás Szelei

หากคุณต้องการที่จะสามารถตั้งค่าผ่านทางบรรทัดคำสั่งคอมไพเลอร์ฉันยังคงใช้ค่าคงที่ในรหัสตัวเองและเพียง #define ค่าที่กำหนดให้กับค่าคงที่
CodesInChaos

12

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

ตัวประมวลผลล่วงหน้าและค่าคงที่มีตำแหน่งการใช้งานที่เหมาะสม

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

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

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

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

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

  5. คัดลอกโค้ดวาง:
    Aka แมโคร นี่ไม่ใช่คำแนะนำในการใช้งานมาโครมากเกินไป - แค่มาโครนั้นมีวิธีที่มีประสิทธิภาพมากกว่าในการใช้งานการเทียบสำเนาที่ผ่านมา แต่ใช้ด้วยความระมัดระวัง และใช้มันหากคุณรู้ว่าคุณกำลังทำอะไรอยู่ แน่นอนว่าคงไม่สามารถทำได้ แต่สามารถใช้inlineฟังก์ชั่นได้เช่นกันหากทำได้ง่าย

ดังนั้นคุณจะใช้ค่าคงที่เมื่อใด
เกือบทุกที่อื่น

  1. การไหลของรหัส Neater:
    โดยทั่วไปเมื่อคุณใช้ค่าคงที่มันแทบจะแยกไม่ออกจากตัวแปรปกติและด้วยเหตุนี้จึงเป็นรหัสที่อ่านได้ดีกว่า หากคุณเขียนรูทีนที่มี 75 บรรทัด - มี 3 หรือ 4 บรรทัดหลังจากทุก 10 บรรทัดที่มี #ifdef จะไม่สามารถอ่านได้มาก อาจได้รับค่าคงที่หลักภายใต้ #ifdef และใช้ในการไหลตามธรรมชาติทุกที่

  2. รหัสเยื้องดี: ทุก preprocessor สั่งไม่เคยทำงานได้ดีกับรหัสมิฉะนั้นเยื้องกัน แม้ว่าคอมไพเลอร์ของคุณอนุญาตการเยื้องของ #def ตัวประมวลผลล่วงหน้า Pre-ANSI C ไม่อนุญาตให้มีช่องว่างระหว่างจุดเริ่มต้นของบรรทัดและอักขระ "#"; "#" นำหน้าจะต้องอยู่ในคอลัมน์แรกเสมอ

  3. การกำหนดค่า:
    อีกเหตุผลหนึ่งที่ว่าทำไมค่าคงที่ / หรือตัวแปรมีเหตุผลก็คือพวกเขาสามารถวิวัฒนาการได้อย่างง่ายดายจากการเชื่อมโยงกับ globals หรือในอนาคตสามารถขยายได้จากไฟล์การกำหนดค่า

สิ่งหนึ่งที่ล่าสุด:
ไม่เคยใช้ preprocessor สั่ง#ifdefการข้ามขอบเขตหรือ#endif { ... }คือจุดเริ่มต้นของ#ifdefหรือวางของด้านข้างแตกต่างกันของ#endif { ... }มันแย่มาก อาจสร้างความสับสนและอาจเป็นอันตรายได้ในบางครั้ง

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


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

1
"ไม่เคยใช้คำสั่ง preprocessor #ifdef ถึง #endif ข้ามขอบเขตหรือ {... }" โดยขึ้นอยู่กับ IDE ถ้า IDE รู้จักบล็อกตัวประมวลผลล่วงหน้าที่ไม่ได้ใช้งานและยุบหรือแสดงด้วยสีที่ต่างกันนั่นไม่ใช่ความสับสน
Abyx

@Abyx มันเป็นความจริงที่ IDE อาจช่วยได้ อย่างไรก็ตามในหลายกรณีที่ผู้คนพัฒนาภายใต้แพลตฟอร์ม * nix (linux ฯลฯ ) - ตัวเลือกของบรรณาธิการ ฯลฯ มีให้เลือกมากมาย (VI, emacs และอื่น ๆ ) ดังนั้นคนอื่นอาจใช้โปรแกรมแก้ไขที่แตกต่างจากของคุณ
Dipan Mehta

4

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


3

จุดเพิ่มเติมเล็กน้อยที่ควรกล่าวถึง:

  1. คอมไพเลอร์สามารถประมวลผลนิพจน์คงที่ซึ่งพรีโปรเซสเซอร์ไม่สามารถทำได้ ตัวอย่างเช่นตัวประมวลผลล่วงหน้าโดยทั่วไปจะไม่มีวิธีการประเมินชนิดsizeof()ที่ไม่ใช่แบบดั้งเดิม สิ่งนี้อาจบังคับใช้if()ในบางกรณี

  2. คอมไพเลอร์จะไม่สนใจปัญหาไวยากรณ์มากที่สุดในรหัสซึ่งจะข้ามใน#if(บางประเด็นที่เกี่ยวข้องกับ preprocessor ยังสามารถก่อให้เกิดปัญหา) if()แต่ยืนยันความถูกต้องประโยครหัสข้ามกับ ดังนั้นหากรหัสซึ่งจะข้ามบางสร้าง แต่ไม่คนอื่น ๆ จะไม่ถูกต้องเป็นผลมาจากการเปลี่ยนแปลงอื่น ๆ ในแฟ้ม (เช่นเปลี่ยนชื่อตัวบ่งชี้) มันโดยทั่วไปจะบ่นเกี่ยวกับการสร้างทั้งหมดถ้ามันปิดการใช้งานด้วยแต่ไม่ถ้ามันเป็นคนพิการโดยif() #ifขึ้นอยู่กับสาเหตุที่รหัสถูกข้ามซึ่งอาจเป็นหรือไม่ดีก็ได้

  3. คอมไพเลอร์บางตัวจะไม่ใช้โค้ดที่ไม่สามารถเข้าถึงได้ การประกาศระยะเวลาการจัดเก็บแบบคงที่ภายในรหัสที่ไม่สามารถเข้าถึงได้อย่างไรก็ตามรวมถึงตัวอักษรสตริงอาจจัดสรรพื้นที่แม้ว่าจะไม่มีรหัสใดสามารถใช้พื้นที่ที่จัดสรรได้

  4. มาโครสามารถใช้if()การทดสอบภายในได้ แต่ไม่มีกลไกใด ๆ สำหรับตัวประมวลผลล่วงหน้าที่จะดำเนินการกับตรรกะแบบมีเงื่อนไขใด ๆ ภายในแมโคร [โปรดทราบว่าสำหรับการใช้งานภายในแมโครตัว? :ดำเนินการอาจดีกว่าifแต่ใช้หลักการเดียวกัน]

  5. #ifสั่งสามารถใช้ในการควบคุมสิ่งที่ได้รับการกำหนดมาโคร

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


1

เรื่องสั้น ๆ สั้น ๆ : ความกลัวเกี่ยวกับคำสั่งพรีโพรเซสเซอร์นั้นโดยทั่วไปแล้ว 2, การรวมหลาย ๆ อันและโค้ดที่อ่านได้น้อยกว่าและตรรกะของ criptic มากกว่า

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


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