[gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

[gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von RobertDebiannutzer » 13.04.2018 10:00:52

Hallo,

ich kompiliere immer gerne u.a. mit den zusätzlichen gcc-Parametern "-pedantic -Wall -Werror -Wextra".
(Je mehr Anfänger man ist, desto strenger muss der Compiler sein... :wink: )

Dabei ist mir schon mehrmals Folgendes aufgefallen, ich zeige es euch hier mal am Beispiel von dmenu.
Hier der Links zum source-tree: https://git.suckless.org/dmenu/tree/

In util.h findet sich u.a. folgendes:

Code: Alles auswählen

#define MAX(A, B)               ((A) > (B) ? (A) : (B))
#define MIN(A, B)               ((A) < (B) ? (A) : (B))
Soweit ich weiß, nennt man diese Ausdrücke "Makros".

Ausschnitt aus congfig.mk (CFLAGS von mir mit -Werror und -Wextra ergänzt):

Code: Alles auswählen

CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS   = -std=c99 -pedantic -Wall -Werror -Wextra -Os ${INCS} ${CPPFLAGS}
LDFLAGS  = -s ${LIBS}

# compiler and linker
CC = cc
/usr/bin/cc ist ein Link zu /usr/bin/gcc (mit Umweg über /etc/alternatives/cc).

Nun beschwert sich also gcc bezüglich dieser Makros:

Code: Alles auswählen

In file included from dmenu.c:19:0:
dmenu.c: In function ‘calcoffsets’:
util.h:4:38: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
 #define MIN(A, B)               ((A) < (B) ? (A) : (B))
                                      ^
dmenu.c:84:32: note: in expansion of macro ‘MIN’
   if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
                                ^~~
util.h:4:50: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
 #define MIN(A, B)               ((A) < (B) ? (A) : (B))
                                                  ^
dmenu.c:84:32: note: in expansion of macro ‘MIN’
   if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
                                ^~~
dmenu.c:84:30: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
   if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
                              ^
In file included from dmenu.c:19:0:
util.h:4:38: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
 #define MIN(A, B)               ((A) < (B) ? (A) : (B))
                                      ^
dmenu.c:87:32: note: in expansion of macro ‘MIN’
   if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
                                ^~~
util.h:4:50: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
 #define MIN(A, B)               ((A) < (B) ? (A) : (B))
                                                  ^
dmenu.c:87:32: note: in expansion of macro ‘MIN’
   if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
                                ^~~
dmenu.c:87:30: error: signed and unsigned type in conditional expression [-Werror=sign-compare]
   if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
Ersetze ich nun die Makros durch entsprechende Funktionen, ist gcc zufrieden:

Code: Alles auswählen

inline int
MAX(int A, int B)
{
	return (A > B) ? A : B;
}
inline int
MIN(int A, int B)
{
	return (A < B) ? A : B;
}
Wobei ich im Fall dmenu "inline"-Funktionen nehmen muss, in einem anderen Fall haben normale Funktionen funktioniert. Aber bei dmenu beschwert sich gcc sonst:

Code: Alles auswählen

drw.o: In function `MAX':
drw.c:(.text+0x2d): multiple definition of `MAX'
dmenu.o:dmenu.c:(.text+0xcdd): first defined here
drw.o: In function `MIN':
drw.c:(.text+0x35): multiple definition of `MIN'
dmenu.o:dmenu.c:(.text+0xce5): first defined here
util.o: In function `MAX':
util.c:(.text+0x0): multiple definition of `MAX'
dmenu.o:dmenu.c:(.text+0xcdd): first defined here
util.o: In function `MIN':
util.c:(.text+0x8): multiple definition of `MIN'
dmenu.o:dmenu.c:(.text+0xce5): first defined here
collect2: error: ld returned 1 exit status
Makefile:28: recipe for target 'dmenu' failed
make: *** [dmenu] Error 1
(das ist sogar ohne -Werror, denn sonst wäre gcc gar nicht soweit gekommen, denn er beschwert sich auch noch wegen drei anderen Stellen, auch wegen "error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]", die haben aber nix mit den MIN/MAX-Funktionen zu tun.

Mag mir vielleicht jemand erklären, wieso ursprünglich Makros verwendet wurden und wie sich Makros und Funktionen in diesem Kontext unterscheiden?
Wegen der "inline"-Funktion: Bei einem anderen Programm, bei dem ich MIN/MAX-Makros durch Funktionen ersetzt habe, standen die im Gegensatz zu dmenu nicht in einer extra Header-Datei. Hat es damit zu tun, dass ich hier eine "inline"-Funktion brauche und bei dem anderen Programm nicht?
Zuletzt geändert von RobertDebiannutzer am 14.04.2018 09:14:23, insgesamt 1-mal geändert.

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von Meillo » 13.04.2018 10:40:13

Die Warnungen zu Vergleichen zwischen signed und unsigned sind schon recht pedantisch. Sie koennen helfen Fehler aufzudecken, aber wenn sie dazu fuehren, dass man anfaengt zu casten, dann geht das ein wenig weit (weil der Code mit Noise zugemuellt wird). Die Frage, die man sich stellen muss, ist, was passiert, wenn der signed-int negativ ist bzw. der unsigned-int groesser als der maximale signed-int ist. Falls diese Faelle nicht vorkommen oder richtig behandelt werden, dann hat man kein Problem und kann die Meldungen ignorieren.

Dass bei der Funktion keine Warnung auftritt liegt daran, dass beim Funktionsaufruf die uebergebenen Parameter automatisch auf die Argumenttypen gecastet werden. Der Vergleich geschieht dann zwischen den zwei gleichen Datentypen der lokalen Argumentvariablen. Darum keine Meldung.

MAX() und MIN() werden oft als Makros implementiert, weil sie dann mit allen Typen funktionieren (float ebenso wie int, und auch mit Pointern), ohne dass man dafuer mehrere Funktionen oder einen gemeinsamen Obertyp braucht.

Bei `inline' kommt keine Meldung, weil die Funktion gar nicht auftaucht, sondern ihr Code beim Kompilieren einfach an die Stelle kopiert wird. Ohne `inline' wird die Funktion angelegt, was zu einem Namenskonflikt fuehrt, weil woanders eine gleichnamige Funktion definiert worden ist. Nenne deine Funktion anders und die Meldung verschwindet.

`-pendantic' und `-Wextra' finde ich fuer gewoehnlich etwas viel. Meist verwende ich `-Wall -Wno-pointer-sign'. Nur fuer sporadische ausfuehrliche Checks verwende ich mal sowas wie `-pedantic', um moeglicherweise problematische Stellen finden und analysieren zu koennen.
Use ed once in a while!

eggy
Beiträge: 3334
Registriert: 10.05.2008 11:23:50

Re: C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von eggy » 13.04.2018 11:07:40

RobertDebiannutzer hat geschrieben: ↑ zum Beitrag ↑
13.04.2018 10:00:52

Wobei ich im Fall dmenu "inline"-Funktionen nehmen muss, in einem anderen Fall haben normale Funktionen funktioniert. Aber bei dmenu beschwert sich gcc sonst:

Code: Alles auswählen

drw.o: In function `MAX':
drw.c:(.text+0x2d): multiple definition of `MAX'
dmenu.o:dmenu.c:(.text+0xcdd): first defined here
drw.o: In function `MIN':
drw.c:(.text+0x35): multiple definition of `MIN'
dmenu.o:dmenu.c:(.text+0xce5): first defined here
util.o: In function `MAX':
util.c:(.text+0x0): multiple definition of `MAX'
dmenu.o:dmenu.c:(.text+0xcdd): first defined here
util.o: In function `MIN':
util.c:(.text+0x8): multiple definition of `MIN'
dmenu.o:dmenu.c:(.text+0xce5): first defined here
collect2: error: ld returned 1 exit status
Makefile:28: recipe for target 'dmenu' failed
make: *** [dmenu] Error 1
Die Ausgabe bedeutet, dass es bereits eine Funktion namens "MAX" gibt ("first defined here" : erstes Vorkommen der Funktionsdefinition). Deswegen geht das da ohne inline schief. Das "inline" bedeutet, dass der Code an der Stelle nicht mehr per Funktionsaufruf im Programm ausgeführt wird, sondern die jeweiligen Befehle vom Präprozessor praktisch an die Stelle im Code geschrieben werden. D.h. Du hast da eigentlich garkeine Funktion, Du ersparst Dir damit nur Schreibarbeit, weil, salopp gesagt, der Compiler am Anfang erst einmal "Suchen und Ersetzen" auf deinem Quelltext spielt.

Bezüglich der Warnungen, es gibt nen gcc ne Möglichkeit mit der sich die Meldungen für bestimmte Codestellen abschalten lassen.
https://gcc.gnu.org/onlinedocs/gcc/Diag ... agmas.html
Und damit kannst Du dann sagen "ja, lieber Compiler, das soll hier so", während an allen anderen Stellen die Warnungen weiterhin erscheinen.

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

[gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von RobertDebiannutzer » 14.04.2018 09:14:04

Danke! :THX:

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von Meillo » 14.04.2018 21:21:35

eggy hat geschrieben: ↑ zum Beitrag ↑
13.04.2018 11:07:40
Das "inline" bedeutet, dass der Code an der Stelle nicht mehr per Funktionsaufruf im Programm ausgeführt wird, sondern die jeweiligen Befehle vom Präprozessor praktisch an die Stelle im Code geschrieben werden.
Kleine Korrektur: Nicht vom Praeprozessor (cpp), sondern vom Compiler (gcc).

Der Praeprozessor reagiert nur auf Zeilen, die mit einem Doppelkreuz (#) beginnen. Die Direktive `#include' arbeitet dort so aehnlich wie `inline': Der Inhalt der angegebenen Datei wird an die Stelle kopiert.

`inline' ist eine Faehigkeit des Compilers, aber AFAIK kein Teil der Sprache. Jedenfalls taucht es nicht im Harbison&Steele auf. AFAIK ist `inline' ein *Hinweis* an den Compiler, diese Funktion *moeglichst* zu inlinen. Das ist AFAIK aber keine Pflicht ... im Gegensatz zu `volatile' beispielsweise, wo ihm verboten wird, die Variable zu optimieren, weil sie sich auf fuer ihn unvorhersehbare Weise aendern kann (Signale, Hardwaremappings, etc.).

Wenn man nicht genau weiss, dass es noetig ist, sollte man auf `inline' verzichten. Optimierende Compiler erkennen oft von selbst wenn irgendwo sinnvollerweise geinlined werden kann und sollte.
Use ed once in a while!

eggy
Beiträge: 3334
Registriert: 10.05.2008 11:23:50

Re: [gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von eggy » 14.04.2018 21:38:23

Meillo: Danke für die Korrektur.
In der manpage zum gcc findet man nen paar Stellen bezüglich inline - dass das inline nicht zwingend umgesetzt wird, hatte ich nicht auf dem Schirm, interessant - wieder was gelernt :THX:

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: [gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von Meillo » 14.04.2018 21:57:54

eggy hat geschrieben: ↑ zum Beitrag ↑
14.04.2018 21:38:23
dass das inline nicht zwingend umgesetzt wird, hatte ich nicht auf dem Schirm, interessant - wieder was gelernt :THX:
Ich bin mir diesbezueglich aber nicht sicher, nur AFAIK. Ich folgere das daraus, dass es nicht zur Sprachdefinition gehoert und folglich nicht von allen Compilern verstanden werden wird. Darum wuerde ich mich nicht darauf verlassen. Es kann aber gut sein, dass der gcc einen `inline'-Befehl immer umsetzt.
Use ed once in a while!

eggy
Beiträge: 3334
Registriert: 10.05.2008 11:23:50

Re: [gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von eggy » 15.04.2018 00:30:49

In der Manpage findet man unter anderem das da:
-Winline
Warn if a function that is declared as inline cannot be inlined. Even with this option, the compiler does not warn about failures to inline functions declared in system headers.

The compiler uses a variety of heuristics to determine whether or not to inline a function. For example, the compiler takes into account the size of the function being inlined and the amount of inlining that has already been done in the current function. Therefore, seemingly insignificant changes in the source program can cause the warnings produced by -Winline to appear or disappear.


-finline-limit=n
By default, GCC limits the size of functions that can be inlined. This flag allows coarse control of this limit. n is the size of functions that can be inlined in number of pseudo instructions.

Inlining is actually controlled by a number of parameters, which may be specified individually by using --param name=value. The -finline-limit=n option sets some of these parameters as follows:

max-inline-insns-single
is set to n/2.

max-inline-insns-auto
is set to n/2.

See below for a documentation of the individual parameters controlling inlining and for the defaults of these parameters.

Note: there may be no value to -finline-limit that results in default behavior.

Note: pseudo instruction represents, in this particular context, an abstract measurement of function's size. In no way does it represent a count of assembly instructions and as such its exact meaning might change from one release to an another.
max-inline-insns-single
Several parameters control the tree inliner used in GCC. This number sets the maximum number of instructions (counted in GCC's internal representation) in a single function that the tree inliner considers for inlining. This only affects functions declared inline and methods implemented in a class declaration (C++). The default value is 400.
und zwei handvoll andere Stellen.

https://gcc.gnu.org/onlinedocs/gcc/Inline.html sagt
Note that certain usages in a function definition can make it unsuitable for inline substitution. Among these usages are: variadic functions, use of alloca, use of computed goto (see Labels as Values), use of nonlocal goto, use of nested functions, use of setjmp, use of __builtin_longjmp and use of __builtin_return or __builtin_apply_args. Using -Winline warns when a function marked inline could not be substituted, and gives the reason for the failure.
Der interessanteste Teil aus dem obrigen Text: "GCC does not inline any functions when not optimizing unless you specify the ‘always_inline’ attribute for the function" - hätte ich auch nicht erwartet.

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: [gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von Meillo » 15.04.2018 07:23:17

@eggy: :THX:
Use ed once in a while!

RobertDebiannutzer
Beiträge: 385
Registriert: 16.06.2017 09:52:36

Re: [gelöst] C: MIN/MAX-Makro vs. (inline-)Funktion

Beitrag von RobertDebiannutzer » 15.04.2018 21:04:08

Sehr interessant, eure weiteren Beiträge! :THX:

Antworten