Функція проти макросу в CMake


89

Офіційний документ 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+.
Андрі,

Відповіді:


93

Я написав зразок коду нижче:

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 Відповідати на власні запитання дозволяється негайно згідно з довідковим центром (особливо якщо це хороше, не дублююче запитання, яке представляє загальний інтерес для інших). Це допомагає ТО стати кращою базою знань. Чи читали ви допис у блозі, зв’язаний у цій темі довідкового центру? stackoverflow.blog/2011/07/01/…
Еміль Корм'є

1
@robert Я просто повідомляю, що сам засновник SO думає про цю практику. Візьми це з собою. ;-)
Еміль Корм'є

2
Запуск таких прикладів із cmake --trace-expandпросвітницьким
MarcH

1
Будь ласка, додайте наступну команду після кожного дзвінка: message("# arg in main scope = '${arg}'")+ виклик функції перед макросом.
MarcH

34

Іншими словами, функція штовхає та видає нову область змінних (змінні, створені та змінені, існують лише у функції), а макрос - ні. Однак ви можете замінити поведінку функції за замовчуванням PARENT_SCOPEпараметром setкоманди.


8

Документація cmake, яку ви цитуєте, настільки оманлива, що в основному є неправильною. Це слід уточнити / виправити таким чином:

  • макрос: при його виклику команди, записані в макросі, спочатку всі модифікуються перед запуском будь-якої , замінюючи формальні параметри ($ {arg1}) переданими аргументами.

cmake --trace-expand показує, що саме відбувається.

Документ cmake 3.13.3 не змінився порівняно з 2.8.12 щодо цього.


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

2

Макророзширення відповіли Yantao З дійсно відкривають очі!

Я також виявив, що підручник нижче містить кілька конкретних прикладів, які корисні для розуміння концепції змінної області дії.

Цитується з 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.