strcmp: gcc vs clang

ninti

Well-Known Member
Hallo!

Ich dachte mir, da hier auch übers Programmieren geredet wird und die zwei Compiler eher was für Unixer sind, deshalb dachte ich, meine Frage wäre hier am besten aufgehoben.

C-Schnipsel:

Code:
    printf ("%d\n", strcmp ("abcde", "abCde"));
     printf ("%d\n", strcmp ("abcde", "abcde"));
     printf ("%d\n", strcmp ("abcd", "abcde"));

Compiler: CLANG
clang -std=c99 -Wall -Wextra -Werror -pedantic -o prog prog.c

Ausgabe:
32
0
-101

Compiler: GCC
gcc -std=c99 -Wall -Wextra -Werror -pedantic -o prog prog.c

Ausgabe:
1
0
-1

Die Frage:
Wieso ist bei clang ein anderer Rückgabewert als bei gcc???
Und was ist das nun für ein Wert? ^^
Mir ist klar:
32 = 1
0 = 0
-1 = -101
Aber kann man dann mit diesen komischen Werten was anderes machen als nur 0 oder 1 erwarten?

Anmerkung von Buch:
Es wurde mit Visual C++ Compiler kompiliert und hat das gleiche Ergebnis wie gcc.
 
Der C Standard schreibt nur echt positiv, 0 und echt negativ vor. Es bietet sich somit an die Differenz der letzten Verglichen Stelle als Rückgabewert zu verwenden.
 
Weil Subtraktion ein Befehl in der ALU ist, während das andere entweder 2x Vergleich macht oder Subtraktion und Vorzeichentests. Und das sind mindestens 2 Befehle. Wozu die CPU mit überflüssigen Befehlen zumüllen, wenn der Standard das nicht erfordert?
 
Das (nakal) ist Teil 1. Teil 2 ist das ggflls. "nebenbei" herbeigeführte setup für JNZ. Das ist billig, elegant und sorgt für tight loops.
 
Weil Subtraktion ein Befehl in der ALU ist
Wass ist ALU? Ich kenn nur Aluminium. :)
Das (nakal) ist Teil 1. Teil 2 ist das ggflls. "nebenbei" herbeigeführte setup für JNZ. Das ist billig, elegant und sorgt für tight loops.
Das bitte für keine Programmierer übersetzen bitte. :)
 
Das bitte für keine Programmierer übersetzen bitte. :)

Um zwei Strings zu vergleichen braucht man eine Schleife, die die einzelnen Zeichen entlang wandert und vergleicht. Das klingt nach 1 job, aber in Wirklichkeit sind es 2, nämlich der Schleifenjob und der Vergleichsjob.

Der Schleifenjob ist es, die Strings entlangzuwandern über deren gesamte Länge. Der Vergleichsjob ist es, die beiden jeweils indizierten Zeichen zu vergleichen. Für beides ist eine Prüfung notwendig. Für die Schleife die, ob der Index noch innerhalb der Stringlänge liegt. Und für den Vergleich eben, ob die Zeichen gleich sind. In beiden Fällen folgt gegebenenfalls ein Sprung. Wenn z.B. die gesamte Länge abgewandert ist, wird aus der Schleife herausgesprungen; ebenso im Falle von Ungleichheit.

Sprünge erfolgen bei Prozessoren meist über sogenannte Flags, hier konkret das "Zero" flag. Dieses wird immer dann ("automatisch" von den Funktionsblöcken der CPU) gesetzt, wenn eine Operation das Ergebnis 0 liefert (In Wirklichkeit ist es etwas komplizierter aber für eine Erklärung taugt das).
Wenn ich nun den Vergleich zweier Zeichen als Subtraktion ausführe, dann wird gegebenenfalls (bei Gleichheit) das Zero flag gesetzt. Warum? Weil "gleich sein" dasselbe ist wie "eins minus das andere ist 0".

Dadurch kann man sehr eleganten und kompakten Schleifen-Code erzeugen. Bei C und anderen 0 delimited Sprachen (sprich, nach dem letzten Zeichen des Inhalts kommt ein Zeichen 0) kann man dieses Spielchen noch weiter treiben. Und auch der Fall ungleich langer Strings ist elegant geregelt weil, wenn ein String länger ist als der andere bei der Operation "Zeichen1 - Zeichen2" = |eins der beiden Zeichen| bzw, noch simpler die Ungleichheitsfälle von "Inhalte verschieden" und "Ein String länger als der andere" vom selben Algoritmus erledigt werden.

Um's noch hübscher zu machen ist damit aber auch der Schleifen job halb mit erledigt, denn im Falle von Ungleichheit (Zero flag nicht gesetzt) kann sie verlassen werden. Alles was noch gebraucht wird ist ein Signal, ob die Schleife wegen Ungleichheit verlassen wurde oder wegen Ende (impliziert Gleichheit). Dabei kann man ausnutzen dass SUB (subtraktion A, B) anders als CMP (compare A, B) auch das rechnerische Ergebnis in einem Register vorhält. Und hierbei gilt, dass dieses jedenfalls 0 ist im Falle von Gleichheit und jedenfalls ungleich 0 bei Ungleichheit. Damit wiederum kann man nach der Schleife erkennen, warum sie verlassen wurde bzw. ob die Strings gleich waren oder nicht (oder wie bei clang sogar noch Details (die aber eigentlich praktisch immer egal sind)).
(Bedenke: Im Fall der Gleichheit wäre das letzte geprüfte Zeichen beider Strings 0 und 0 - 0 ergibt 0 und setzt das Zero flag.)
 
Was in diesem ganzen Thread zum vollständigen Verständnis noch fehlt, ist das Wort "builtin". Praktisch alle modernen Compiler haben sehr simpel gesagt eine "Funktionalitätserkennung", sie versuchen sich wiederholende Paradigmen im Code zu erkennen und ersetzen sie durch spezielle, oft handassemblierte Funktionen. Diese Funktionen sind schneller als der automatisch generierte Code. In der Praxis sind Bibliotheksfunktionen beliebte Opfer für Builtins, denn sie haben ein definiertes Verhalten und man kann sie einfach durch simples "suchen und ersetzen" austauschen. Die GCC macht es bei C und C++ gleich für einen ganzen Berg Funktionen: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

Code:
#include <stdio.h>

int
main(void)
{
   printf("%d\n", strncmp("bla", "blu", 3));

   return 0;
}

FreeBSDs libc implementiert strncmp() so:
Code:
int
strncmp(const char *s1, const char *s2, size_t n)
{

   if (n == 0)
     return (0);
   do {
     if (*s1 != *s2++)
       return (*(const unsigned char *)s1 -
         *(const unsigned char *)(s2 - 1));
     if (*s1++ == '\0')
       break;
   } while (--n != 0);
   return (0);
}
Normalerweise ersetzt gcc das strncmp() durch __builtin_strncmp():
Code:
% gcc49 -o test test.c
% ./test
-1

Wenn wir die Builtins abschalten, wird es nicht ersetzt und die Funktion aus FreeBSDs libc verwendet:
Code:
% gcc49 -fno-builtin -o test test.c
% ./test
-20

Da nicht jeder Compiler die gleichen Builtins unterstützt und sie teilweise anders implementiert, können unterschiedliche Compiler unterschiedliche Ergebnisse geben.
 
Ähm okay, danke euch, dass muss ich erst mal 10x lesen, damit ichs verstehe. ^^

Aber was sind eigentlich diese Builtiuns?
Ist das Funktionalitätserkennung?

So weit ich auch gelesen habe, hat ja jeder Compiler so einen Scanner, der nach Zeichen sucht und diese dann umsetzt, ist das mit Builtin gemeint?
 
Builtin kann man einfach mit "eingebaute Funktion" übersetzen. Der Compiler hat eine eigene Implementierung einer Funktion, die z.B. auch in der libc vorhanden ist. Er ersetzt dann diese Funktion beim Kompilieren durch seine eigene.

Rob
 
Ist denn der Code mit gcc compiliert - schneller, als der mit clang? :)

Wenn wir die Builtins abschalten, wird es nicht ersetzt und die Funktion aus FreeBSDs libc verwendet:
Hat clang denn keine Builtins?

Builtin kann man einfach mit "eingebaute Funktion" übersetzen. Der Compiler hat eine eigene Implementierung einer Funktion, die z.B. auch in der libc vorhanden ist. Er ersetzt dann diese Funktion beim Kompilieren durch seine eigene.
Wozu werden dann überhaupt Bibliotheken benutzt, wenn der Compiler seine eigene hat?
 
Worum es geht ist Folgendes:

Compiler sind eher blöd. Naturgemäß. Was passiert da eigentlich? Es fängt mit Text an (Source Code). Der wird bröckchenweise eingelesen und jedem Bröckchen wird erst mal zugeordnet, was es ist - immer noch als Text. Also, eine Folge von Ziffern, ein Punkt, eine Klammer, eine Folge von Zeichen, usw. Danach werden diese "vorsortierten Bröckchen" mit vorgegebenen Mustern verglichen. Also, Ziffernfolge dann Punkt, dann Ziffernfolge ... ist wohl eine Fließkommazahl. Zeichenfolge, die einem der vorgegebenen Datentypen entspricht (z.B. "init") gefolgt von Leerzeichen und noch einer Zeichenfolge (die den Vorgaben für einen Variablennamen entsprechen) gefolgt von optionalen Leerzeichen, gefolgt von Semikolon ... ist wohl eine Variablendeklaration usw, usw.

Irgendwann gibts als wesentliches Ergebnis der Zeichenfolgen/Text Verarbeitung und Auswertung einen AST (Abstract Syntax Tree) und diverse Informationen, z.B. zu Variablen/Speicher, etc.
Also, im Grunde wieder lauter kleine Bröckchen, nur diesmal in einer sinnvollen Struktur geordnet. Beispiel (sinngemäß, menschenverständlich ausgedrückt) gibts da irgendwo die "Bröckchengruppe" Speicherstelle (passend für integer), Addition, feste Zahl (z.B. 5). Das könnte aus einer Source Code Stelle wie der Folgenden entstanden sein: "... int x; (und irgendwo später) ... x += 5;"
Ein dämlicher oder alter Compiler macht daraus etwas wie (Pseudo Assembler)
Code:
LOAD Register1, [Address of x]
SET Register2, 5
ADD Register1, Register2
STORE [Address of x], Register1

Will vor allem zweierlei heissen: a) der Compiler "kennt" die CPU und deren Regeln und Eigenheiten (z.B. dass ADD nur mit Registern geht und ADD Register1, 5 nicht möglich ist, sondern 5 erst in ein anderes Register geschrieben werden muss) und b) der Compiler ist dämlich und ersetzt im Grunde nur eine Textform (source code) durch eine andere Form (Maschinencode).
Nun könnte "x" eine Variable sein, die alle paar Ewigkeiten mal gebraucht wird. Aber es könnte auch eine Variable sein, die Aberzigtausende Male pro Sekunde z.B. in einer Schleife gebraucht wird. Ein dämlicher/alter Compiler macht da keinen Unterschied. Ein etwas modernerer Compiler "erkennt" das aber und erzeugt je nachdem unterschiedlichen Code oder zumindest unterschiedliche Speicherung.

Das Ganz kann man auch weiter treiben und sehen, dass manche Routinen sehr teuer (CPU) sind oder aber sehr, sehr häufig vorkommen oder sehr häufig in Schleifen verwendet werden und es sich lohnt, da zu optimieren. Dazu bieten sich natürlich diverse Routinen in Standard libraries geradezu an.

Und das sind dann built-ins. Will heissen, da wird nicht der Code verwendet, den der Compiler - nach wie vor teilweise erheblich schlechter als der Mensch - stumpfsinnig erzeugt, sondern eben von Hand optimierter Code (Siehe auch die JNZ Frage).
 
Ist denn der Code mit gcc compiliert - schneller, als der mit clang?

Kann man nicht so einfach vergleichen. GCC ist * vornehmtuerisch räusper * historisch gewachsen (sprich ein unüberschaubarer aber auch hoch gezüchteter Verhau von Code, aber prinzipiell auf veralteter Technologie basierend). clang dagegen ist ziemlich modern und von Anfang darauf hin getrimmt vielseitige "Massage Möglichkeiten" und Optimierungen zu bieten.

Und noch etwas ist wesentlich: clang hat ganz erheblich besseren intermediate code (ungefähr wie Assembler aber völlig CPU unabhängig), der, das ist entscheidend, ziemlich viel "Sinngehalt" transportieren kann, während frühere engines recht primitive replacement engines waren.


Hat clang denn keine Builtins?
Wozu werden dann überhaupt Bibliotheken benutzt, wenn der Compiler seine eigene hat?

Die Frage macht so relativ wenig Sinn, weil clang ganz anders tickt und ganz allgemein bessere Optimierungsmöglichkeiten bietet.

Ausserdem ersetzen built-ins keineswegs libraries. Sie ersetzen lediglich einige Routinen in libraries und sind CPU abhängig; für x86 gibt's wohl mehr, für Exoten eher weniger.
 
Danke! Langsam komme ich dahinter. :)
Und noch etwas ist wesentlich: clang hat ganz erheblich besseren intermediate code (ungefähr wie Assembler aber völlig CPU unabhängig), der, das ist entscheidend, ziemlich viel "Sinngehalt" transportieren kann, während frühere engines recht primitive replacement engines waren.
Ist clang jetzt plattformunabhängiger als gcc? :)
Dh. portabler?
 
Jein.

(Noch) unterstützt gcc mehr Plattformen und Architekturen. Aber für clang lässt sich leichter und schneller Unterstützung für neue Plattformen bauen.

Ich denke mal, dass momentan zunehmend Gleichstand herrscht (in Akzeptanz und Nutzung) und clang mittelfristig an gcc vorbeiziehen wird.

Noch zwei eher private Anmerkungen, durchaus freundlich wenn auch relativ deutlich: ich bin ja geduldig, aber ninti-Privatunterricht hatte ich nicht geplant.
Und: ich persönlich finde ja, dass eine gewisse Ausgeglichenheit zwischen Fragen und Antworten sein sollte. Sicher darf man viele Fragen, auch relativ blutige Anfängerfragen haben, aber dann sollte man sich auch fragen, wie glaubwürdig so mancher Kommentar wohl wirkt ...

Wikipedia hat übrigen zu allerlei Themen interessante Literaturverweise.
 
wie glaubwürdig so mancher Kommentar wohl wirkt
Das verstehe ich nun gar nicht, ich bin dabei ein Buch zu lesen und C zu lernen und mehr als ein Buch zu lesen kann ich nicht.
 
Back
Top