ฟังก์ชันอินไลน์เทียบกับมาโครตัวประมวลผลล่วงหน้า


115

ฟังก์ชันอินไลน์แตกต่างจากมาโครตัวประมวลผลล่วงหน้าอย่างไร


คำตอบสำหรับ stackoverflow.com/questions/132738/… มีข้อมูลบางอย่างที่เกี่ยวข้องกับคำถามของคุณ
Luc Touraille

คำตอบ:


127

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

ฟังก์ชันอินไลน์คือฟังก์ชันจริงที่ร่างกายถูกฉีดเข้าไปในไซต์การโทรโดยตรง สามารถใช้ได้เฉพาะในกรณีที่การเรียกใช้ฟังก์ชันเหมาะสมเท่านั้น

ตอนนี้สำหรับการใช้มาโครเทียบกับฟังก์ชันอินไลน์ในบริบทที่เหมือนฟังก์ชันโปรดทราบว่า:

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

39
ฟังก์ชั่นอินไลน์ไม่ได้รับการรับประกันว่าจะอินไลน์เสมอไป: เนื่องจากคอมไพเลอร์จะไม่อินไลน์หากทำเช่นนั้นจะสร้างโค้ดที่ช้าลงเป็นต้นคอมไพเลอร์ทำการวิเคราะห์จำนวนมากที่ Engineer ไม่สามารถทำได้และทำสิ่งที่ถูกต้อง
Martin York

14
ฉันเชื่อว่าฟังก์ชันแบบเรียกซ้ำเป็นอีกตัวอย่างหนึ่งที่คอมไพเลอร์ส่วนใหญ่ไม่สนใจการอินไลน์
LBushkin

มีความแตกต่างที่สำคัญใน C เมื่อเทียบกับ C ++ ในกรณีนี้หรือไม่?
rzetterberg

7
ประเด็นหนึ่งที่ไม่ได้กล่าวถึงคือการซับในอาจได้รับอิทธิพลจากแฟล็กการคอมไพล์ ตัวอย่างเช่นเมื่อคุณสร้างสำหรับความเร็วสูงสุด (เช่น GCC -O2 / -O3) คอมไพเลอร์จะเลือกที่จะอินไลน์หลายฟังก์ชัน แต่เมื่อคุณสร้างสำหรับขนาดต่ำสุด (-Os) ถ้าโดยทั่วไปจะเรียกฟังก์ชันอินไลน์เพียงครั้งเดียว (หรือฟังก์ชันที่เล็กมาก ) ด้วยมาโครไม่มีทางเลือกดังกล่าว
dbrank0

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

78

ขั้นแรกมาโครตัวประมวลผลก่อนเป็นเพียง "คัดลอกวาง" ในโค้ดก่อนการคอมไพล์ ดังนั้นจึงไม่มีการตรวจสอบประเภทและผลข้างเคียงบางอย่างอาจปรากฏขึ้น

ตัวอย่างเช่นหากคุณต้องการเปรียบเทียบ 2 ค่า:

#define max(a,b) ((a<b)?b:a)

ผลข้างเคียงจะปรากฏขึ้นหากคุณใช้max(a++,b++)เช่น ( aหรือbจะเพิ่มขึ้นสองครั้ง) ให้ใช้ (ตัวอย่าง) แทน

inline int max( int a, int b) { return ((a<b)?b:a); }

3
เพียงต้องการเพิ่มในตัวอย่างของคุณว่านอกจากผลข้างเคียงแล้วมาโครยังสามารถเพิ่มภาระงานได้อีกด้วยพิจารณาว่าอันmax(fibonacci(100), factorial(10000))ที่ใหญ่กว่าจะได้รับการคำนวณสองครั้ง :(
watashiSHUN

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

16

ฟังก์ชันอินไลน์ถูกขยายโดยคอมไพลเลอร์โดยที่มาโครถูกขยายโดยตัวประมวลผลล่วงหน้าซึ่งเป็นเพียงการทดแทนข้อความดังนั้น

  • ไม่มีการตรวจสอบประเภทระหว่างการเรียกใช้มาโครในขณะที่การตรวจสอบประเภทเสร็จสิ้นระหว่างการเรียกใช้ฟังก์ชัน

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

    #define MAX(a,b) ((a)>(b) ? (a) : (b))
    int i = 5, j = MAX(i++, 0);
    

    จะส่งผลให้

    int i = 5, j = ((i++)>(0) ? (i++) : (0));
  • อาร์กิวเมนต์แมโครไม่ได้รับการประเมินก่อนการขยายแมโคร

    #define MUL(a, b) a*b
    int main()
    {
      // The macro is expended as 2 + 3 * 3 + 5, not as 5*8
      printf("%d", MUL(2+3, 3+5));
     return 0;
    }
    // Output: 16`
    
  • ไม่สามารถใช้คีย์เวิร์ด return ในมาโครเพื่อส่งคืนค่าเช่นเดียวกับในกรณีของฟังก์ชัน

  • ฟังก์ชันอินไลน์สามารถทำงานได้มากเกินไป

  • โทเค็นที่ส่งผ่านไปยังมาโครสามารถเชื่อมต่อกันได้โดยใช้ตัวดำเนินการ ## ที่เรียกว่าตัวดำเนินการวางโทเค็น

  • โดยทั่วไปแล้วมาโครจะใช้สำหรับการใช้โค้ดซ้ำโดยที่ฟังก์ชันอินไลน์ถูกใช้เพื่อกำจัดเวลาเหนือศีรษะ (เวลาส่วนเกิน) ระหว่างการเรียกใช้ฟังก์ชัน (หลีกเลี่ยงการข้ามไปยังรูทีนย่อย)


13

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

ต่อไปนี้เป็นประเด็นที่ไม่ชัดเจนอื่น ๆ อีกมากมายที่ระบุไว้


11

หากต้องการเพิ่มความแตกต่างให้กับสิ่งที่ให้ไปแล้ว: คุณไม่สามารถก้าวผ่านตัว#defineแก้ไขข้อบกพร่อง แต่คุณสามารถก้าวผ่านฟังก์ชันอินไลน์ได้



3

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

  • ฟังก์ชันอินไลน์เป็นไปตามโปรโตคอลทั้งหมดของประเภทความปลอดภัยที่บังคับใช้กับฟังก์ชันปกติ
  • ฟังก์ชันอินไลน์ถูกระบุโดยใช้ไวยากรณ์เดียวกันกับฟังก์ชันอื่น ๆ ยกเว้นว่าจะรวมคีย์เวิร์ดอินไลน์ไว้ในการประกาศฟังก์ชัน
  • นิพจน์ที่ส่งผ่านเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอินไลน์จะได้รับการประเมินหนึ่งครั้ง
  • ในบางกรณีนิพจน์ที่ส่งเป็นอาร์กิวเมนต์ไปยังมาโครสามารถประเมินได้มากกว่าหนึ่งครั้ง http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx

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

- บทความดีๆ : http://www.codeguru.com/forum/showpost.php?p=1093923&postcount=1

;


2

ฟังก์ชันอินไลน์จะรักษาความหมายของค่าไว้ในขณะที่มาโครตัวประมวลผลล่วงหน้าจะคัดลอกไวยากรณ์เท่านั้น คุณจะได้รับบั๊กที่ละเอียดมากด้วยมาโครตัวประมวลผลล่วงหน้าหากคุณใช้อาร์กิวเมนต์หลายครั้งตัวอย่างเช่นหากอาร์กิวเมนต์มีการกลายพันธุ์เช่น "i ++" ที่มีการดำเนินการสองครั้งนั้นค่อนข้างน่าแปลกใจ ฟังก์ชันอินไลน์จะไม่มีปัญหานี้


1

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


1

หากต้องการทราบความแตกต่างระหว่างมาโครและฟังก์ชันอินไลน์อันดับแรกเราควรทราบว่ามันคืออะไรและควรใช้เมื่อใด

ฟังก์ชั่น :

int Square(int x){
return(x*X);
}
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • การเรียกใช้ฟังก์ชันมีค่าใช้จ่ายที่เกี่ยวข้องเนื่องจากหลังจากฟังก์ชันเสร็จสิ้นการเรียกใช้งานจะต้องทราบว่าต้องส่งคืนที่ใดและต้องเก็บค่าไว้ในหน่วยความจำสแต็ก

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

แมโคร:

# define Square(x) x*x;
int main()
{
int value = 5;
int result = Square(value);
cout << result << endl;
}
  • มาโครทำงานในขั้นตอนก่อนการประมวลผลกล่าวคือในขั้นตอนนี้ข้อความที่เขียนด้วย # คำหลักจะถูกแทนที่ด้วยเนื้อหาเช่น

ผลลัพธ์ int = กำลังสอง (x * x)

แต่มาโครมีจุดบกพร่องที่เกี่ยวข้อง

#define Square(x) x*x
int main() {
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

นี่ออกเป็น11 ไม่ได้ 36

ฟังก์ชั่นออนไลน์ :

inline int Square(int x) {
    return x * x;
}

int main() {
    using namespace std;
    int val = 5;
    int result = Square(val + 1);
    cout << result << endl;
    return 0;
}

เอาต์พุต36

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

การเปรียบเทียบระหว่างมาโครและฟังก์ชันอินไลน์:

  1. มาโครทำงานผ่านการแทนที่ในขณะที่ในฟังก์ชันอินไลน์การเรียกฟังก์ชันจะถูกแทนที่ด้วยร่างกาย
  2. มาโครมีข้อผิดพลาดได้ง่ายเนื่องจากมีการทดแทนในขณะที่ฟังก์ชันอินไลน์นั้นปลอดภัยในการใช้งาน
  3. มาโครไม่มีที่อยู่ในขณะที่ฟังก์ชันแบบอินไลน์มีที่อยู่
  4. มาโครใช้โค้ดหลายบรรทัดได้ยากในขณะที่ฟังก์ชันอินไลน์ไม่ได้ใช้งาน
  5. ในมาโคร C ++ ไม่สามารถใช้กับฟังก์ชันสมาชิกได้ในขณะที่ฟังก์ชันอินไลน์อาจเป็นได้

สรุป:

บางครั้งฟังก์ชันแบบอินไลน์มีประโยชน์มากกว่ามาโครเนื่องจากช่วยเพิ่มประสิทธิภาพและปลอดภัยในการใช้งานและลดค่าใช้จ่ายในการเรียกฟังก์ชันด้วย เป็นเพียงการร้องขอไปยังคอมไพเลอร์ฟังก์ชันบางอย่างจะไม่อยู่ในบรรทัดเช่น:

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

ซึ่งเป็นสิ่งที่ดีเพราะนั่นคือเมื่อใดก็ตามที่คอมไพเลอร์คิดว่าดีที่สุดที่จะทำสิ่งอื่นด้วยวิธีอื่น


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

0

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

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

#define MACRO_FUNC(X) ...

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

if(MACRO_FUNC(y)) {
 ...body
}

สามารถใช้ฟังก์ชันปกติได้โดยไม่มีปัญหา


0

จากมุมมองของการเข้ารหัสฟังก์ชันอินไลน์ก็เหมือนกับฟังก์ชัน ดังนั้นความแตกต่างระหว่างฟังก์ชันแบบอินไลน์และมาโครจึงเหมือนกับความแตกต่างระหว่างฟังก์ชันและมาโคร

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

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


0

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


-1
#include<iostream>
using namespace std;
#define NUMBER 10 //macros are preprocessed while functions are not.
int number()
{ 
    return 10;
}
/*In macros, no type checking(incompatible operand, etc.) is done and thus use of micros can lead to errors/side-effects in some cases. 
However, this is not the case with functions.
Also, macros do not check for compilation error (if any). Consider:- */
#define CUBE(b) b*b*b
int cube(int a)
{
 return a*a*a;
}
int main()
{
 cout<<NUMBER<<endl<<number()<<endl;
 cout<<CUBE(1+3); //Unexpected output 10
 cout<<endl<<cube(1+3);// As expected 64
 return 0;
}

โดยทั่วไปมาโครจะเร็วกว่าฟังก์ชันเนื่องจากไม่เกี่ยวข้องกับค่าใช้จ่ายในการเรียกฟังก์ชันจริง

ข้อเสียบางประการของมาโคร: ไม่มีการตรวจสอบประเภทยากที่จะดีบักเนื่องจากทำให้เกิดการเปลี่ยนอย่างง่ายมาโครไม่มีเนมสเปซดังนั้นมาโครในส่วนหนึ่งของโค้ดอาจส่งผลต่อส่วนอื่น ๆ มาโครอาจทำให้เกิดผลข้างเคียงดังที่แสดงในตัวอย่าง CUBE () ด้านบน

มาโครมักจะเป็นซับเดียว อย่างไรก็ตามอาจประกอบด้วยมากกว่าหนึ่งบรรทัดฟังก์ชันไม่มีข้อ จำกัด ดังกล่าว


คุณจะได้รับความสนุกสนานมากแค่ไหนจาก#define TWO_N(n) 2 << nนั้นcout << CUBE(TWO_N(3 + 1)) << endl;? (เป็นการดีกว่าที่จะสิ้นสุดบรรทัดเอาต์พุตด้วยendlดีกว่าที่จะเริ่มต้นด้วย)
Jonathan Leffler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.