GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Ein Leitfaden zum Verständnis von Linux-Softwarebibliotheken in C

Softwarebibliotheken sind seit langem eine einfache und sinnvolle Möglichkeit, Code wiederzuverwenden. In diesem Artikel wird erläutert, wie Sie Bibliotheken von Grund auf neu erstellen und sie Clients zur Verfügung stellen. Obwohl die beiden Beispielbibliotheken auf Linux abzielen, gelten die Schritte zum Erstellen, Veröffentlichen und Verwenden dieser Bibliotheken auch für andere Unix-ähnliche Systeme.

Die Beispielbibliotheken sind in C geschrieben, was für die Aufgabe gut geeignet ist. Der Linux-Kernel ist größtenteils in C geschrieben, der Rest in Assemblersprache. (Das Gleiche gilt für Windows- und Linux-Vettern wie macOS.) Die Standard-Systembibliotheken für Ein-/Ausgabe, Netzwerk, Zeichenfolgenverarbeitung, Mathematik, Sicherheit, Datenverschlüsselung usw. sind ebenfalls hauptsächlich in C geschrieben. Um eine Bibliothek zu schreiben C soll also in der Muttersprache von Linux geschrieben werden. Darüber hinaus setzt C die Leistungsmarke unter den Hochsprachen.

Weitere Linux-Ressourcen

  • Spickzettel für Linux-Befehle
  • Spickzettel für fortgeschrittene Linux-Befehle
  • Kostenloser Online-Kurs:RHEL Technical Overview
  • Spickzettel für Linux-Netzwerke
  • SELinux-Spickzettel
  • Spickzettel für allgemeine Linux-Befehle
  • Was sind Linux-Container?
  • Unsere neuesten Linux-Artikel

Es gibt auch zwei Beispielclients (einer in C, der andere in Python), um auf die Bibliotheken zuzugreifen. Es überrascht nicht, dass ein C-Client auf eine in C geschriebene Bibliothek zugreifen kann, aber der Python-Client zeigt, dass eine in C geschriebene Bibliothek Clients aus anderen Sprachen bedienen kann.

Statische versus dynamische Bibliotheken

Linux-Systeme haben zwei Arten von Bibliotheken:

  • Eine statische Bibliothek (auch bekannt als Bibliotheksarchiv) wird während der Verknüpfungsphase des Kompilierungsprozesses in einen statisch kompilierten Client (z. B. einen in C oder Rust) gebacken. Tatsächlich erhält jeder Client seine eigene Kopie der Bibliothek. Ein erheblicher Nachteil einer statischen Bibliothek tritt in den Vordergrund, wenn die Bibliothek überarbeitet werden muss (z. B. um einen Fehler zu beheben), da jeder Bibliotheksclient erneut mit der statischen Bibliothek verknüpft werden muss. Eine nachfolgend beschriebene dynamische Bibliothek vermeidet diesen Mangel.
  • Eine dynamische (auch bekannt als gemeinsam genutzte) Bibliothek wird während der Verknüpfungsphase eines statisch kompilierten Clientprogramms gekennzeichnet, aber das Clientprogramm und der Bibliothekscode bleiben ansonsten bis zur Laufzeit unverbunden – der Bibliothekscode wird nicht in den Client gebacken. Zur Laufzeit verbindet der dynamische Loader des Systems eine gemeinsam genutzte Bibliothek mit einem ausführenden Client, unabhängig davon, ob der Client aus einer statisch kompilierten Sprache wie C oder einer dynamisch kompilierten Sprache wie Python stammt. Als Ergebnis kann eine dynamische Bibliothek aktualisiert werden, ohne Clients zu belästigen. Schließlich können mehrere Clients eine einzelne Kopie einer dynamischen Bibliothek gemeinsam nutzen.

Im Allgemeinen werden dynamische Bibliotheken gegenüber statischen bevorzugt, obwohl die Komplexität und Leistung auf Kosten gehen. So wird jeder Bibliothekstyp erstellt und veröffentlicht:

  1. Der Quellcode für die Bibliothek wird in ein oder mehrere Objektmodule kompiliert, bei denen es sich um Binärdateien handelt, die in eine Bibliothek aufgenommen und mit ausführbaren Clients verknüpft werden können.
  2. Die Objektmodule sind in einer einzigen Datei gepackt. Für eine statische Bibliothek ist die Standarderweiterung .a für "Archiv". Für eine dynamische Bibliothek ist die Erweiterung .so für "gemeinsames Objekt". Die beiden Beispielbibliotheken, die dieselbe Funktionalität haben, werden als Dateien libprimes.a veröffentlicht (statisch) und libshprimes.so (dynamisch). Das Präfix lib wird für beide Arten von Bibliotheken verwendet.
  3. Die Bibliotheksdatei wird in ein Standardverzeichnis kopiert, sodass Client-Programme problemlos auf die Bibliothek zugreifen können. Ein typischer Ort für die Bibliothek, ob statisch oder dynamisch, ist /usr/lib oder /usr/local/lib; andere Orte sind möglich.

Detaillierte Schritte zum Erstellen und Veröffentlichen der einzelnen Bibliothekstypen folgen in Kürze. Zunächst werde ich jedoch die C-Funktionen in den beiden Bibliotheken vorstellen.

Die Funktionen der Beispielbibliothek

Die beiden Beispielbibliotheken werden aus denselben fünf C-Funktionen erstellt, von denen vier für Client-Programme zugänglich sind. Die fünfte Funktion, die ein Dienstprogramm für eine der anderen vier ist, zeigt, wie C das Verbergen von Informationen unterstützt. Der Quellcode für jede Funktion ist kurz genug, dass die Funktionen in einer einzigen Quelldatei untergebracht werden können, obwohl mehrere Quelldateien (z. B. eine für jede der vier veröffentlichten Funktionen) eine Option sind.

Die Bibliotheksfunktionen sind elementar und behandeln auf verschiedene Weise Primzahlen. Alle Funktionen erwarten vorzeichenlose (d. h. nicht negative) Integer-Werte als Argumente:

  • Der is_prime Funktion testet, ob ihr einzelnes Argument eine Primzahl ist.
  • Die are_coprimes Die Funktion prüft, ob ihre beiden Argumente einen größten gemeinsamen Teiler (ggT) von 1 haben, was Teilerzahlen definiert.
  • Die prime_factors Die Funktion listet die Primfaktoren ihres Arguments auf.
  • Der goldbach Die Funktion erwartet einen geraden ganzzahligen Wert von 4 oder mehr und listet auf, welche zwei Primzahlen dieses Argument ergeben; es kann mehrere Summierungspaare geben. Die Funktion ist nach dem Mathematiker Christian Goldbach aus dem 18. Jahrhundert benannt, dessen Vermutung, dass jede gerade ganze Zahl größer als zwei die Summe zweier Primzahlen ist, eines der ältesten ungelösten Probleme der Zahlentheorie bleibt.

Die Hilfsfunktion gcd befindet sich in den bereitgestellten Bibliotheksdateien, aber auf diese Funktion kann außerhalb der sie enthaltenden Datei nicht zugegriffen werden; Daher kann ein Bibliotheksclient den gcd nicht direkt aufrufen Funktion. Ein genauerer Blick auf C-Funktionen verdeutlicht den Punkt.

Mehr zu C-Funktionen

Jede Funktion in C hat eine Speicherklasse, die den Geltungsbereich der Funktion bestimmt. Für Funktionen gibt es zwei Möglichkeiten:

  • Die Standardspeicherklasse für Funktionen ist extern , was einer Funktion einen globalen Geltungsbereich verleiht. Ein Client-Programm kann jeden extern aufrufen Funktion in den Beispielbibliotheken. Hier ist die Definition für die Funktion are_coprimes mit einem expliziten extern :
    extern unsigned are_coprimes(unsigned n1, unsigned n2) {
      ...
    }
  • Die Speicherklasse static begrenzt den Gültigkeitsbereich einer Funktion auf die Datei, in der die Funktion definiert ist. In den Beispielbibliotheken ist die Hilfsfunktion gcd ist static :
    static unsigned gcd(unsigned n1, unsigned n2) {
      ...
    }

Funktioniert nur innerhalb der primes.c Datei kann gcd aufrufen , und nur die Funktion are_coprimes tut dies. Wenn die statischen und dynamischen Bibliotheken erstellt und veröffentlicht werden, können andere Programme einen extern aufrufen Funktion wie are_coprimes aber nicht der static Funktion gcd . Die static Die Speicherklasse verbirgt somit den gcd Funktion von Bibliotheksclients, indem der Umfang der Funktion auf die anderen Bibliotheksfunktionen beschränkt wird.

Die Funktionen außer gcd in der primes.c file muss keine Speicherklasse angeben, die standardmäßig extern wäre . In Bibliotheken ist es jedoch üblich, den extern zu machen explizit.

C unterscheidet zwischen Funktionsdefinitionen und Deklarationen, was für Bibliotheken wichtig ist. Beginnen wir mit Definitionen. C hat nur benannte Funktionen, und jede Funktion ist definiert mit:

  • Ein eindeutiger Name. Keine zwei Funktionen in einem Programm dürfen den gleichen Namen haben.
  • Eine Argumentliste, die leer sein kann. Die Argumente werden eingegeben.
  • Entweder ein Rückgabewerttyp (z. B. int für eine 32-Bit-Ganzzahl mit Vorzeichen) oder void wenn kein Wert zurückgegeben wird.
  • Ein in geschweiften Klammern eingeschlossener Körper. In einem erfundenen Beispiel könnte der Körper leer sein.

Jede Funktion in einem Programm muss genau einmal definiert werden.

Hier ist die vollständige Definition für die Bibliotheksfunktion are_coprimes :

extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* definition */
  return 1 == gcd(n1, n2); /* greatest common divisor of 1? */
}

Die Funktion gibt einen booleschen Wert zurück (entweder 0 für falsch oder 1 für wahr), je nachdem, ob die beiden Integer-Argumente einen größten gemeinsamen Teiler von 1 haben. Die Hilfsfunktion gcd berechnet den größten gemeinsamen Teiler von ganzzahligen Argumenten n1 und n2 .

Eine Funktionsdeklaration hat im Gegensatz zu einer Definition keinen Hauptteil:

extern unsigned are_coprimes(unsigned n1, unsigned n2); /* declaration */

Die Deklaration endet mit einem Semikolon nach der Argumentliste; es gibt keine geschweiften Klammern, die einen Körper umschließen. Eine Funktion kann innerhalb eines Programms mehrfach deklariert werden.

Warum werden überhaupt Deklarationen benötigt? In C muss eine aufgerufene Funktion für ihren Aufrufer sichtbar sein. Es gibt verschiedene Möglichkeiten, eine solche Sichtbarkeit bereitzustellen, je nachdem, wie wählerisch der Compiler ist. Ein sicherer Weg besteht darin, die aufgerufene Funktion über ihrem Aufrufer zu definieren, wenn sich beide in derselben Datei befinden:

void f() {...}     /* f is defined before being called */
void g() { f(); }  /* ok */

Die Definition der Funktion f könnte unter den Aufruf der Funktion g verschoben werden wenn f wurden über dem Aufruf deklariert:

void f();         /* declaration makes f visible to caller */
void g() { f(); } /* ok */
void f() {...}    /* easier to put this above the call from g */

Was aber, wenn sich die aufgerufene Funktion in einer anderen Datei befindet als ihr Aufrufer? Wie werden Funktionen, die in einer Datei definiert sind, in einer anderen Datei sichtbar gemacht, da jede Funktion genau einmal in einem Programm definiert werden muss?

Dieses Problem wirkt sich auf statische oder dynamische Bibliotheken aus. Beispielsweise sind die Funktionen in den beiden Primes-Bibliotheken in der Quelldatei primes.c definiert , von denen sich Binärkopien in jeder Bibliothek befinden; aber diese definierten Funktionen müssen für einen Bibliotheksclient in C sichtbar sein, das ein separates Programm mit eigenen Quelldateien ist.

Die Bereitstellung von Sichtbarkeit über Dateien hinweg ist das, was Funktionsdeklarationen leisten können. Für die "primes"-Beispiele gibt es eine Header-Datei namens primes.h die die vier Funktionen deklariert, die für Bibliotheksclients in C sichtbar gemacht werden sollen:

/** header file primes.h: function declarations **/
extern unsigned is_prime(unsigned);
extern void prime_factors(unsigned);
extern unsigned are_coprimes(unsigned, unsigned);
extern void goldbach(unsigned);

Diese Deklarationen dienen als Schnittstelle, indem sie die Aufrufsyntax für jede Funktion spezifizieren.

Zur Bequemlichkeit des Clients wird die Textdatei primes.h könnte in einem Verzeichnis im Suchpfad des C-Compilers gespeichert werden. Typische Speicherorte sind /usr/include und /usr/local/include . Ein C-Client würde #include diese Header-Datei am Anfang des Quellcodes des Clients. (Eine Header-Datei wird somit in den „Kopf“ einer anderen Quelldatei importiert.) C-Header-Dateien dienen auch als Eingabe für Dienstprogramme (z. B. Rusts bindgen ), die Clients in anderen Sprachen den Zugriff auf eine C-Bibliothek ermöglichen.

Zusammenfassend lässt sich sagen, dass eine Bibliotheksfunktion genau einmal definiert, aber bei Bedarf deklariert wird. jeder Bibliotheksclient in C benötigt die Deklaration. Eine Header-Datei sollte Funktionsdeklarationen enthalten, aber keine Funktionsdefinitionen. Wenn eine Header-Datei Definitionen enthielt, konnte die Datei mehrfach in ein C-Programm eingebunden werden, wodurch die Regel verletzt wurde, dass eine Funktion genau einmal in einem C-Programm definiert werden muss.

Der Quellcode der Bibliothek

Unten ist der Quellcode für zwei Bibliotheken. Dieser Code, die Header-Datei und die beiden Beispielclients sind auf meiner Website verfügbar.

Die Bibliothek funktioniert

#include <stdio.h>
#include <math.h>

extern unsigned is_prime(unsigned n) {
  if (n <= 3) return n > 1;                   /* 2 and 3 are prime */
  if (0 == (n % 2) || 0 == (n % 3)) return 0; /* multiples of 2 or 3 aren't */

  /* check that n is not a multiple of other values < n */
  unsigned i;
  for (i = 5; (i * i) <= n; i += 6)
    if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* not prime */

  return 1; /* a prime other than 2 or 3 */
}

extern void prime_factors(unsigned n) {
  /* list 2s in n's prime factorization */
  while (0 == (n % 2)) {  
    printf("%i ", 2);
    n /= 2;
  }

  /* 2s are done, the divisor is now odd */
  unsigned i;
  for (i = 3; i <= sqrt(n); i += 2) {
    while (0 == (n % i)) {
      printf("%i ", i);
      n /= i;
    }
  }

  /* one more prime factor? */
  if (n > 2) printf("%i", n);
}

/* utility function: greatest common divisor */
static unsigned gcd(unsigned n1, unsigned n2) {
  while (n1 != 0) {
    unsigned n3 = n1;
    n1 = n2 % n1;
    n2 = n3;
  }
  return n2;
}

extern unsigned are_coprimes(unsigned n1, unsigned n2) {
  return 1 == gcd(n1, n2);
}

extern void goldbach(unsigned n) {
  /* input errors */
  if ((n <= 2) || ((n & 0x01) > 0)) {
    printf("Number must be > 2 and even: %i is not.\n", n);
    return;
  }

  /* two simple cases: 4 and 6 */
  if ((4 == n) || (6 == n)) {
    printf("%i = %i + %i\n", n, n / 2, n / 2);
    return;
  }
 
  /* for n >= 8: multiple possibilities for many */
  unsigned i;
  for (i = 3; i < (n / 2); i++) {
    if (is_prime(i) && is_prime(n - i)) {
      printf("%i = %i + %i\n", n, i, n - i);
      /* if one pair is enough, replace this with break */
    }
  }
}

Diese Funktionen dienen als Wasser für die Mühle der Bibliothek. Die beiden Bibliotheken stammen aus genau demselben Quellcode und der Header-Datei primes.h ist die C-Schnittstelle für beide Bibliotheken.

Erstellen der Bibliotheken

Die Schritte zum Erstellen und Veröffentlichen statischer und dynamischer Bibliotheken unterscheiden sich in einigen Details. Für die statische Bibliothek sind nur drei Schritte erforderlich, für die dynamische Bibliothek nur zwei weitere. Die zusätzlichen Schritte beim Erstellen der dynamischen Bibliothek spiegeln die zusätzliche Flexibilität des dynamischen Ansatzes wider. Beginnen wir mit der statischen Bibliothek.

Die Quelldatei der Bibliothek primes.c wird in ein Objektmodul kompiliert. Hier ist der Befehl mit dem Prozentzeichen als Systemeingabeaufforderung (doppelte Kreuzzeichen leiten meine Kommentare ein):

% gcc -c primes.c ## step 1 static

Dies erzeugt die Binärdatei primes.o , das Objektmodul. Das Flag -c bedeutet nur kompilieren.

Der nächste Schritt besteht darin, das/die Objektmodul(e) mithilfe von Linux ar zu archivieren Dienstprogramm:

% ar -cvq libprimes.a primes.o ## step 2 static

Die drei Flags -cvq stehen für „create“, „verbose“ und „quick append“ (falls neue Dateien zu einem Archiv hinzugefügt werden müssen). Denken Sie daran, dass das Präfix lib ist Standard, aber der Bibliotheksname ist willkürlich. Natürlich muss der Dateiname für eine Bibliothek eindeutig sein, um Konflikte zu vermeiden.

Das Archiv ist bereit zur Veröffentlichung:

% sudo cp libprimes.a /usr/local/lib ## step 3 static

Die statische Bibliothek ist jetzt für Clients zugänglich, Beispiele dafür sind in Kürze verfügbar. (Die sudo ist enthalten, um die richtigen Zugriffsrechte zum Kopieren einer Datei nach /usr/local/lib sicherzustellen .)

Die dynamische Bibliothek erfordert auch ein oder mehrere Objektmodule zum Packen:

% gcc primes.c -c -fpic ## step 1 dynamic

Das hinzugefügte Flag -fpic weist den Compiler an, positionsunabhängigen Code zu generieren, der ein binäres Modul ist, das nicht in einen festen Speicherplatz geladen werden muss. Eine solche Flexibilität ist in einem System mehrerer dynamischer Bibliotheken kritisch. Das resultierende Objektmodul ist etwas größer als das für die statische Bibliothek generierte.

Hier ist der Befehl zum Erstellen der einzelnen Bibliotheksdatei aus dem/den Objektmodul(en):

% gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## step 2 dynamic

Das Flag -shared gibt an, dass die Bibliothek gemeinsam genutzt (dynamisch) und nicht statisch ist. Das -Wl Flag führt eine Liste von Compiler-Optionen ein, von denen die erste den soname der dynamischen Bibliothek festlegt , was erforderlich ist. Der soname gibt zuerst den logischen Namen der Bibliothek an (libshprimes.so ) und dann nach dem -o Flag, der physische Dateiname der Bibliothek (libshprimes.so.1 ). Das Ziel ist es, den logischen Namen konstant zu halten, während sich der physische Dateiname mit neuen Versionen ändern kann. In diesem Beispiel die 1 am Ende des physischen Dateinamens libshprimes.so.1 stellt die erste Version der Bibliothek dar. Die logischen und physischen Dateinamen können identisch sein, aber die beste Vorgehensweise besteht darin, unterschiedliche Namen zu verwenden. Ein Client greift auf die Bibliothek über ihren logischen Namen zu (in diesem Fall libshprimes.so). ), wie ich in Kürze erläutern werde.

Der nächste Schritt besteht darin, die gemeinsam genutzte Bibliothek für Clients leicht zugänglich zu machen, indem sie in das entsprechende Verzeichnis kopiert wird. zum Beispiel wieder /usr/local/lib again:

% sudo cp libshprimes.so.1 /usr/local/lib ## step 3 dynamic

Nun wird ein symbolischer Link zwischen dem logischen Namen der Shared Library (libshprimes.so ) und seinen vollständigen physischen Dateinamen (/usr/local/lib/libshprimes.so.1 ). Am einfachsten geben Sie den Befehl mit /usr/local/lib ein als Arbeitsverzeichnis:

% sudo ln --symbolic libshprimes.so.1 libshprimes.so ## step 4 dynamic

Der logische Name libshprimes.so sollte sich nicht ändern, aber das Ziel des symbolischen Links (libshrimes.so.1 ) kann nach Bedarf für neue Bibliotheksimplementierungen aktualisiert werden, die Fehler beheben, die Leistung steigern und so weiter.

Der letzte Schritt (ein Vorsichtsschritt) ist der Aufruf von ldconfig Utility, das den dynamischen Loader des Systems konfiguriert. Diese Konfiguration stellt sicher, dass der Loader die neu veröffentlichte Bibliothek findet:

% sudo ldconfig ## step 5 dynamic

Die dynamische Bibliothek ist jetzt für Kunden bereit, einschließlich der beiden folgenden Beispiele.

Ein C-Bibliotheksclient

Der Beispiel-C-Client ist der Programmtester, dessen Quellcode mit zwei #include beginnt Anweisungen:

#include <stdio.h>  /* standard input/output functions */
#include <primes.h> /* my library functions */

Die spitzen Klammern um die Dateinamen zeigen an, dass diese Header-Dateien im Suchpfad des Compilers zu finden sind (im Fall von primes.h , das Verzeichnis /usr/local/include ). Ohne dieses #include , würde sich der Compiler über fehlende Deklarationen für Funktionen wie is_prime beschweren und prime_factors , die in beiden Bibliotheken veröffentlicht sind. Übrigens muss der Quellcode für das Testerprogramm überhaupt nicht geändert werden, um jede der beiden Bibliotheken zu testen.

Die Quelldatei für die Bibliothek (primes.c ) wird mit diesen #include geöffnet Anweisungen:

#include <stdio.h>
#include <math.h>

Die Header-Datei math.h wird benötigt, da die Bibliotheksfunktion prime_factors ruft die mathematische Funktion sqrt auf in der Standardbibliothek libm.so .

Als Referenz ist hier der Quellcode für das Testprogramm:

Das Testerprogramm

#include <stdio.h>
#include <primes.h>

int main() {
  /* is_prime */
  printf("\nis_prime\n");
  unsigned i, count = 0, n = 1000;
  for (i = 1; i <= n; i++) {
    if (is_prime(i)) {
      count++;
      if (1 == (i % 100)) printf("Sample prime ending in 1: %i\n", i);
    }
  }
  printf("%i primes in range of 1 to a thousand.\n", count);

  /* prime_factors */
  printf("\nprime_factors\n");
  printf("prime factors of 12: ");
  prime_factors(12);
  printf("\n");
 
  printf("prime factors of 13: ");
  prime_factors(13);
  printf("\n");
 
  printf("prime factors of 876,512,779: ");
  prime_factors(876512779);
  printf("\n");

  /* are_coprimes */
  printf("\nare_coprime\n");
  printf("Are %i and %i coprime? %s\n",
         21, 22, are_coprimes(21, 22) ? "yes" : "no");
  printf("Are %i and %i coprime? %s\n",
         21, 24, are_coprimes(21, 24) ? "yes" : "no");

  /* goldbach */
  printf("\ngoldbach\n");
  goldbach(11);    /* error */
  goldbach(4);     /* small one */
  goldbach(6);     /* another */
  for (i = 100; i <= 150; i += 2) goldbach(i);

  return 0;
}

Beim Kompilieren von tester.c in eine ausführbare Datei ist der knifflige Teil die Reihenfolge der Link-Flags. Denken Sie daran, dass die beiden Beispielbibliotheken mit dem Präfix lib beginnen , und jeder hat die übliche Erweiterung:.a für die statische Bibliothek libprimes.a und .so für die dynamische Bibliothek libshprimes.so . In einer Link-Spezifikation ist das Präfix lib und die Verlängerung fallen weg. Ein Link-Flag beginnt mit -l (kleines L), und ein Kompilierungsbefehl kann viele Link-Flags enthalten. Hier ist der vollständige Kompilierungsbefehl für das Testprogramm, wobei die dynamische Bibliothek als Beispiel verwendet wird:

% gcc -o tester tester.c -lshprimes -lm

Das erste Link-Flag identifiziert die Bibliothek libshprimes.so und das zweite Link-Flag identifiziert die Standard-Mathematikbibliothek libm.so .

Der Linker ist faul, was bedeutet, dass die Reihenfolge der Link-Flags wichtig ist. Beispielsweise erzeugt das Umkehren der Reihenfolge der Linkspezifikationen einen Kompilierungsfehler:

% gcc -o tester tester.c -lm -lshprimes ## danger!

Das Flag, das auf libm.so verweist kommt zuerst, aber keine Funktion aus dieser Bibliothek wird explizit im Testprogramm aufgerufen; daher verlinkt der Linker nicht auf math.so Bücherei. Der Aufruf von sqrt Bibliotheksfunktion kommt nur in prime_factors vor Funktion, die jetzt in der libshprimes.so enthalten ist Bücherei. Der resultierende Fehler beim Kompilieren des Testprogramms ist:

primes.c: undefined reference to 'sqrt'

Dementsprechend sollte die Reihenfolge der Link-Flags dem Linker mitteilen, dass sqrt Funktion wird benötigt:

% gcc -o tester tester.c -lshprimes -lm ## -lshprimes 1st

Der Linker übernimmt den Aufruf der Bibliotheksfunktion sqrt in der libshprimes.so Bibliothek und stellt daher den entsprechenden Link zur Mathematikbibliothek libm.so her . Es gibt eine kompliziertere Option zum Verlinken, die beide Link-Flag-Reihenfolgen unterstützt; In diesem Fall ist es jedoch am einfachsten, die Link-Flags entsprechend anzuordnen.

Hier ist eine Ausgabe einer Ausführung des Tester-Clients:

is_prime
Sample prime ending in 1: 101
Sample prime ending in 1: 401
...
168 primes in range of 1 to a thousand.

prime_factors
prime factors of 12: 2 2 3
prime factors of 13: 13
prime factors of 876,512,779: 211 4154089

are_coprime
Are 21 and 22 coprime? yes
Are 21 and 24 coprime? no

goldbach
Number must be > 2 and even: 11 is not.
4 = 2 + 2
6 = 3 + 3
...
32 =  3 + 29
32 = 13 + 19
...
100 =  3 + 97
100 = 11 + 89
...

Für den goldbach Funktion kann sogar ein relativ kleiner gerader Wert (z. B. 18) mehrere Paare von Primzahlen haben, die sich summieren (in diesem Fall 5 + 13 und 7 + 11). Solche multiplen Primzahlpaare gehören zu den Faktoren, die einen versuchten Beweis von Goldbachs Vermutung erschweren.

Abschluss mit einem Python-Client

Python ist im Gegensatz zu C keine statisch kompilierte Sprache, was bedeutet, dass der Beispiel-Python-Client auf die dynamische und nicht auf die statische Version der Primes-Bibliothek zugreifen muss. Zu diesem Zweck verfügt Python über verschiedene Module (Standard und Drittanbieter), die ein Foreign Function Interface (FFI) unterstützen, das es einem in einer Sprache geschriebenen Programm ermöglicht, in einer anderen Sprache geschriebene Funktionen aufzurufen. Python ctypes ist ein standardmäßiges und relativ einfaches FFI, das es Python-Code ermöglicht, C-Funktionen aufzurufen.

Jedes FFI hat Herausforderungen, da die Schnittstellensprachen wahrscheinlich nicht genau die gleichen Datentypen haben. Beispielsweise verwendet die primes-Bibliothek den C-Typ unsigned int , die Python nicht hat; die ctypes FFI bildet ein C unsigned int ab zu einem Python int . Von den vier extern C-Funktionen, die in der Primes-Bibliothek veröffentlicht sind, verhalten sich in Python mit expliziten ctypes besser Konfiguration.

Die C-Funktionen prime_factors und goldbach void haben statt eines Rückgabetyps, sondern ctypes ersetzt standardmäßig das C void mit dem Python int . Wenn sie aus Python-Code aufgerufen werden, geben die beiden C-Funktionen dann einen zufälligen (daher bedeutungslosen) ganzzahligen Wert aus dem Stack zurück. Allerdings ctypes kann so konfiguriert werden, dass die Funktionen None zurückgeben (Pythons Nulltyp) stattdessen. Hier ist die Konfiguration für die prime_factors Funktion:

primes.prime_factors.restype = None

Eine ähnliche Anweisung behandelt den goldbach Funktion.

Die folgende interaktive Sitzung (in Python 3) zeigt, dass die Schnittstelle zwischen einem Python-Client und der Primes-Bibliothek unkompliziert ist:

>>> from ctypes import cdll

>>> primes = cdll.LoadLibrary("libshprimes.so") ## logical name

>>> primes.is_prime(13)
1
>>> primes.is_prime(12)
0

>>> primes.are_coprimes(8, 24)
0
>>> primes.are_coprimes(8, 25)
1

>>> primes.prime_factors.restype = None
>>> primes.goldbach.restype = None

>>> primes.prime_factors(72)
2 2 2 3 3

>>> primes.goldbach(32)
32 = 3 + 29
32 = 13 + 19

Die Funktionen in der Primes-Bibliothek verwenden nur einen einfachen Datentyp, unsigned int . Wenn diese C-Bibliothek komplizierte Typen wie Strukturen verwendet und wenn Zeiger auf Strukturen an Bibliotheksfunktionen übergeben und von diesen zurückgegeben werden, dann ist ein FFI leistungsfähiger als ctypes könnte für eine reibungslose Schnittstelle zwischen Python und C besser sein. Nichtsdestotrotz sind die ctypes Beispiel zeigt, dass ein Python-Client eine in C geschriebene Bibliothek verwenden kann. Tatsächlich ist die beliebte NumPy-Bibliothek für wissenschaftliches Rechnen in C geschrieben und wird dann in einer High-Level-Python-API bereitgestellt.

Die einfache Primes-Bibliothek und die fortschrittliche NumPy-Bibliothek unterstreichen, dass C die Lingua Franca unter den Programmiersprachen bleibt. Nahezu jede Sprache kann mit C kommunizieren – und über C mit jeder anderen Sprache, die mit C kommuniziert. Python kommuniziert problemlos mit C, und als weiteres Beispiel kann Java dasselbe tun, wenn Project Panama eine Alternative zu Java Native Interface (JNI) wird ).


Linux
  1. Eine praktische Anleitung zum Erlernen von awk

  2. Verstehen von systemd beim Start unter Linux

  3. Linux-Desktop verstehen?

  4. Einsteigerleitfaden für die Linux-Softwareverwaltung mit RPM

  5. Wo lege ich Bibliotheken von Drittanbietern ab, um eine C++-Linux-Entwicklungsumgebung einzurichten?

Grundlegendes zu Linux-Dateiberechtigungen

Linux-Befehle - Vollständiges Handbuch

Vollständige Anleitung zur Installation von Linux auf Chromebook

Ein Leitfaden für Anfänger zur Netzwerkfehlerbehebung unter Linux

Prozesse unter Linux verstehen

Top 10+ der besten Bibliotheksverwaltungssoftware für Linux-Systeme