Funktion vs. Makro in CMake

85

Das offizielle Dokument von CMake 2.8.12 sagt übermacro

Wenn es aufgerufen wird, werden die im Makro aufgezeichneten Befehle zuerst geändert, indem formale Parameter ($ {arg1}) durch die übergebenen Argumente ersetzt und dann als normale Befehle aufgerufen werden.

und über function

Beim Aufrufen werden die in der Funktion aufgezeichneten Befehle zuerst geändert, indem formale Parameter ($ {arg1}) durch die übergebenen Argumente ersetzt und dann als normale Befehle aufgerufen werden.

Offensichtlich sind zwei Zitate fast gleich, aber verwirrend. Ersetzt man zuerst Parameter, wenn man eine Funktion wie ein Makro aufruft?

Yantao Xie
quelle
8
Es gibt mindestens einen weiteren wichtigen, wenn auch ziemlich offensichtlichen Unterschied zwischen functionund macro: die Semantik von return(): Bei Verwendung in a macrokehren Sie nicht vom Makro zurück, sondern von der aufrufenden Funktion.
Joachim W
1
Ein weiterer wichtiger Hinweis ist, dass ein Makro eine Zwei-Pass-Erweiterungsstufe für Argumente hat, wenn eine Funktion nur eine ist. Versuchen Sie, diese Makros und Funktionen zu erstellen, und drucken Sie die ${ARGV}von innen: macro(my_macro), function(my_func). Und nutzen sie: set(a 123), my_macro("\\\${a}\\\\;\\\;;"), my_func(\${a}\\;\;;). Sie werden feststellen, dass Sie alle Zeichen doppelt maskieren $müssen \ , ;um die gesamte Zeichenfolge unverändert an die verschachtelten Befehle zu übergeben. Dies ist aktuell in der cmake 3.14+.
Andry

Antworten:

89

Ich habe unten einen Beispielcode geschrieben:

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})

und die Ausgabe ist:

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

Es scheint argalso, dass der Wert varbeim Aufrufen zugewiesen wird Foound ${arg}nur die Zeichenfolge ${var}beim Aufrufen durch ersetzt wird Moo.

Also ich denke , die beiden oben genannten Zitate sind sehr leicht verwirrt zu machen, obwohl die offiziellen Dokumente auch gesagt , dass :

Beachten Sie, dass die Parameter für ein Makro und Werte wie ARGN keine Variablen im üblichen CMake-Sinne sind. Sie sind String-Ersetzungen, ähnlich wie der C-Präprozessor es mit einem Makro tun würde. Wenn Sie echte CMake-Variablen und / oder eine bessere CMake-Bereichssteuerung wünschen, sollten Sie sich den Funktionsbefehl ansehen.

Yantao Xie
quelle
Ich habe das vergessen, aber ich denke, es kann sein.
Yantao Xie
2
@robert Die sofortige Beantwortung Ihrer eigenen Frage ist gemäß der Hilfe gestattet (insbesondere, wenn es sich um eine gute, nicht doppelte Frage handelt, die für andere von allgemeinem Interesse ist). Dies soll SO helfen, eine bessere Wissensbasis zu werden. Haben Sie den Blog-Beitrag gelesen, der in diesem Hilfethema verlinkt ist? stackoverflow.blog/2011/07/01/…
Emile Cormier
1
@ Robert Ich erzähle nur, was der Gründer von SO selbst über diese Praxis denkt. Nimm es mit ihm auf. ;-)
Emile Cormier
2
Beispiele wie dieses mit cmake --trace-expand
laufen
1
Bitte fügen Sie nach jedem Aufruf den folgenden Befehl hinzu: message("# arg in main scope = '${arg}'")+ Aufruf der Funktion vor dem Makro.
MarcH
33

Mit anderen Worten, die Funktion pusht und öffnet einen neuen Variablenbereich (erstellte und geänderte Variablen existieren nur in der Funktion), das Makro nicht. Sie können jedoch das Standardverhalten der Funktion mit dem PARENT_SCOPEParameter des setBefehls überschreiben .

Robert
quelle
7

Die von Ihnen zitierte cmake-Dokumentation ist so irreführend, dass sie grundsätzlich falsch ist. Es sollte wie folgt geklärt / behoben werden:

  • Makro: Wenn es aufgerufen wird, werden die im Makro aufgezeichneten Befehle zuerst alle geändert, bevor sie ausgeführt werden, indem formale Parameter ($ {arg1}) durch die übergebenen Argumente ersetzt werden.

cmake --trace-expand zeigt genau was passiert.

Das cmake 3.13.3-Dokument hat sich diesbezüglich gegenüber 2.8.12 nicht geändert.

März
quelle
2

Ein weiterer bemerkenswerter Unterschied zwischen function()und macro()ist das Verhalten von return().

Aus der cmake-Dokumentation von return () :

Beachten Sie, dass ein Makro im Gegensatz zu einer Funktion an Ort und Stelle erweitert wird und daher return () nicht verarbeiten kann.

Da es also an Ort und Stelle erweitert wird, macro()kehrt es vom Anrufer zurück. Während einer Funktion wird nur die beendetfunction()

Beispiel:

macro(my_macro)
    return()
endmacro()

function(my_function)
    return()
endfunction()

my_function()
message(hello) # is printed
my_macro()
message(hi) # is not printed
Adam Zahran
quelle
1

Die Makro-Erweiterung, die von Yantao Xie beantwortet wurde, öffnet mir wirklich die Augen!

Ich fand auch, dass das folgende Tutorial einige konkrete Beispiele enthält, die hilfreich sind, um das Konzept des variablen Bereichs zu verstehen.

Zitiert von Learn cmake in 15 Minuten :

In CMake können Sie ein Paar function/ endfunctionBefehle verwenden, um eine Funktion zu definieren. Hier ist eine, die den numerischen Wert ihres Arguments verdoppelt und dann das Ergebnis druckt:

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

doubleIt("4")                           # Prints: 8

Funktionen laufen in ihrem eigenen Bereich. Keine der in einer Funktion definierten Variablen verschmutzt den Bereich des Aufrufers. Wenn Sie einen Wert zurückgeben möchten, können Sie den Namen einer Variablen an Ihre Funktion übergeben und dann den setBefehl mit dem speziellen Argument aufrufen 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

In ähnlicher Weise definiert ein Paar von macro/ endmacroBefehlen ein Makro. Im Gegensatz zu Funktionen werden Makros im selben Bereich wie ihr Aufrufer ausgeführt. Daher werden alle in einem Makro definierten Variablen im Bereich des Aufrufers festgelegt. Wir können die vorherige Funktion durch folgende ersetzen:

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

Sowohl Funktionen als auch Makros akzeptieren eine beliebige Anzahl von Argumenten. Unbenannte Argumente werden der Funktion als Liste über eine spezielle Variable mit dem Namen angezeigt ARGN.

Hier ist eine Funktion, die jedes empfangene Argument verdoppelt und jedes in einer separaten Zeile druckt:

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
Izana
quelle