ฟังก์ชันเทียบกับมาโครใน CMake


90

เอกสารอย่างเป็นทางการของ CMake 2.8.12กล่าวเกี่ยวกับmacro

เมื่อเรียกใช้คำสั่งที่บันทึกไว้ในมาโครจะถูกแก้ไขก่อนโดยแทนที่พารามิเตอร์แบบเป็นทางการ ($ {arg1}) ด้วยอาร์กิวเมนต์ที่ส่งผ่านจากนั้นจึงเรียกใช้เป็นคำสั่งปกติ

และเกี่ยวกับ function

เมื่อเรียกใช้คำสั่งที่บันทึกไว้ในฟังก์ชันจะถูกแก้ไขก่อนโดยแทนที่พารามิเตอร์ที่เป็นทางการ ($ {arg1}) ด้วยอาร์กิวเมนต์ที่ส่งผ่านจากนั้นจึงเรียกใช้เป็นคำสั่งปกติ

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


8
มีความสำคัญอย่างน้อยหนึ่งอย่างแม้ว่าจะมีความแตกต่างที่ชัดเจนพอสมควรระหว่างfunctionและmacro: ความหมายของreturn(): เมื่อใช้ใน a macroคุณจะไม่กลับมาจากมาโคร แต่มาจากฟังก์ชันการโทร
Joachim W

1
ข้อสังเกตที่สำคัญอีกประการหนึ่งคือมาโครมีขั้นตอนการขยายสองรอบบนอาร์กิวเมนต์เมื่อฟังก์ชันเป็นเพียงฟังก์ชันเดียว ลองสร้างมาโครและฟังก์ชันเหล่านี้แล้วพิมพ์${ARGV}จากด้านใน: macro(my_macro), function(my_func). set(a 123)และใช้พวกเขา: my_macro("\\\${a}\\\\;\\\;;"), my_func(\${a}\\;\;;), คุณจะพบว่าคุณจะต้องหลบหนีคู่ทั้งหมด$, \ , ;ที่จะต้องผ่านสตริงทั้งหมดไม่เปลี่ยนแปลงกับคำสั่งที่ซ้อนกัน สิ่งนี้เกิดขึ้นจริงในไฟล์cmake 3.14+.
Andry

คำตอบ:


95

ฉันเขียนโค้ดตัวอย่างด้านล่าง:

set(var "ABC")

macro(Moo arg)
  message("arg = ${arg}")
  set(arg "abc")
  message("# After change the value of arg.")
  message("arg = ${arg}")
endmacro()
message("=== Call macro ===")
Moo(${var})

function(Foo arg)
  message("arg = ${arg}")
  set(arg "abc")
  message("# After change the value of arg.")
  message("arg = ${arg}")
endfunction()
message("=== Call function ===")
Foo(${var})

และผลลัพธ์คือ:

=== Call macro ===
arg = ABC
# After change the value of arg.
arg = ABC
=== Call function ===
arg = ABC
# After change the value of arg.
arg = abc

ดังนั้นดูเหมือนว่าargมีการกำหนดค่าของvarเมื่อโทรFooและ${arg}เป็นเพียงสตริงแทนที่ด้วยเมื่อโทร${var}Moo

ดังนั้นฉันคิดว่าสองคำพูดข้างต้นง่ายมากที่จะทำให้สับสนแม้ว่าเอกสารอย่างเป็นทางการจะบอกว่า :

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


ฉันลืมไปแล้ว แต่ฉันคิดว่ามันอาจจะเป็น
Yantao Xie

2
@robert การตอบคำถามของคุณเองในทันทีจะได้รับอนุญาตตามศูนย์ช่วยเหลือ (โดยเฉพาะอย่างยิ่งถ้าเป็นคำถามที่ดีและไม่ซ้ำซ้อนกับผู้อื่น) นี่คือการช่วยให้ SO กลายเป็นฐานความรู้ที่ดีขึ้น คุณได้อ่านบล็อกโพสต์ที่เชื่อมโยงในหัวข้อศูนย์ช่วยเหลือนั้นหรือไม่ stackoverflow.blog/2011/07/01/…
Emile Cormier

1
@robert ฉันแค่ถ่ายทอดสิ่งที่ผู้ก่อตั้ง SO คิดเกี่ยวกับแนวทางปฏิบัตินี้ ขึ้นกับเขา ;-)
Emile Cormier

2
เล่นตัวอย่างเช่นนี้มีcmake --trace-expandเป็น enlightening
มีนาคม

1
โปรดพิจารณาเพิ่มคำสั่งต่อไปนี้หลังการโทรแต่ละครั้ง: message("# arg in main scope = '${arg}'")+ เรียกใช้ฟังก์ชันก่อนมาโคร
มีนาคม

34

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


8

เอกสาร cmake ที่คุณยกมานั้นทำให้เข้าใจผิดว่าโดยพื้นฐานแล้วมันผิด ควรชี้แจง / แก้ไขดังนี้:

  • แมโคร: เมื่อเรียกใช้คำสั่งที่บันทึกในมาโครจะถูกแก้ไขก่อนที่จะเรียกใช้โดยแทนที่พารามิเตอร์ที่เป็นทางการ ($ {arg1}) ด้วยอาร์กิวเมนต์ที่ส่งผ่าน

cmake --trace-expand แสดงให้เห็นว่าเกิดอะไรขึ้น

cmake 3.13.3 doc ไม่ได้เปลี่ยนแปลงเมื่อเทียบกับ 2.8.12 ในส่วนนี้


3

การขยายมาโครตอบโดย Yantao Xieเปิดตาของฉันจริงๆ!

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

อ้างจากLearn cmake ใน 15 นาที :

ใน CMake คุณสามารถใช้คู่ของfunction/ endfunctionคำสั่งเพื่อกำหนดฟังก์ชัน นี่คือค่าที่เป็นตัวเลขสองเท่าของอาร์กิวเมนต์จากนั้นพิมพ์ผลลัพธ์:

function(doubleIt VALUE)
    math(EXPR RESULT "${VALUE} * 2")
    message("${RESULT}")
endfunction()

doubleIt("4")                           # Prints: 8

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

function(doubleIt VARNAME VALUE)
    math(EXPR RESULT "${VALUE} * 2")
    set(${VARNAME} "${RESULT}" PARENT_SCOPE)    # Set the named variable in caller's scope
endfunction()

doubleIt(RESULT "4")                    # Tell the function to set the variable named RESULT
message("${RESULT}")                    # Prints: 8

ในทำนองเดียวกันคู่ของmacro/ endmacroคำสั่งกำหนดมาโคร มาโครต่างจากฟังก์ชันตรงที่ทำงานในขอบเขตเดียวกับผู้โทร ดังนั้นตัวแปรทั้งหมดที่กำหนดภายในมาโครจึงถูกตั้งค่าในขอบเขตของผู้โทร เราสามารถแทนที่ฟังก์ชันก่อนหน้าด้วยสิ่งต่อไปนี้:

macro(doubleIt VARNAME VALUE)
    math(EXPR ${VARNAME} "${VALUE} * 2")        # Set the named variable in caller's scope
endmacro()

doubleIt(RESULT "4")                    # Tell the macro to set the variable named RESULT
message("${RESULT}")                    # Prints: 8

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

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

function(doubleEach)
    foreach(ARG ${ARGN})                # Iterate over each argument
        math(EXPR N "${ARG} * 2")       # Double ARG's numeric value; store result in N
        message("${N}")                 # Print N
    endforeach()
endfunction()

doubleEach(5 6 7 8)                     # Prints 10, 12, 14, 16 on separate lines

3

อีกความแตกต่างที่โดดเด่นระหว่างfunction()และเป็นพฤติกรรมของmacro()return()

จากเอกสาร cmake ของ return () :

โปรดสังเกตว่ามาโครซึ่งแตกต่างจากฟังก์ชันถูกขยายในตำแหน่งดังนั้นจึงไม่สามารถจัดการกับ return () ได้

ดังนั้นเนื่องจากมีการขยายในสถานที่macro()จึงส่งกลับจากผู้โทร ในขณะที่อยู่ในฟังก์ชั่นเพียงแค่ออกจากfunction()

ตัวอย่าง:

macro(my_macro)
    return()
endmacro()

function(my_function)
    return()
endfunction()

my_function()
message(hello) # is printed
my_macro()
message(hi) # is not printed

0

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

สำหรับรายละเอียดเพิ่มเติม: https://levelup.gitconnected.com/cmake-variable-scope-f062833581b7


พฤติกรรมของพวกเขาแตกต่างกันอย่างไร? หากไม่มีรายละเอียดเพิ่มเติมสิ่งนี้จะไม่ตอบคำถามจริงๆยกเว้นตามลิงค์ นอกจากนี้คุณได้โพสต์ข้อความและลิงก์เดียวกันนี้สามครั้งในคืนนี้ หากคำตอบเดียวกันตอบคำถามหลายข้อให้พิจารณาตั้งค่าสถานะว่าซ้ำกัน
Jeremy Caney

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