Recherche de site Web

Un guide pour comprendre les bibliothèques de logiciels Linux en C


Les bibliothèques de logiciels constituent un moyen simple et judicieux de réutiliser du code.

Les bibliothèques de logiciels constituent un moyen ancien, simple et judicieux de réutiliser du code. Cet article explique comment créer des bibliothèques à partir de zéro et les mettre à la disposition des clients. Bien que les deux exemples de bibliothèques ciblent Linux, les étapes de création, de publication et d'utilisation de ces bibliothèques s'appliquent à d'autres systèmes de type Unix.

Les bibliothèques d'échantillons sont écrites en C, ce qui est bien adapté à la tâche. Le noyau Linux est écrit principalement en C et le reste en langage assembleur. (Il en va de même pour les cousins Windows et Linux tels que macOS.) Les bibliothèques système standard pour les entrées/sorties, la mise en réseau, le traitement des chaînes, les mathématiques, la sécurité, le codage des données, etc. sont également écrites principalement en C. Pour écrire une bibliothèque en Le C consiste donc à écrire dans le langage natif de Linux. De plus, C établit la référence en matière de performances parmi les langages de haut niveau.

Il existe également deux exemples de clients (un en C, l'autre en Python) pour accéder aux bibliothèques. Il n'est pas surprenant qu'un client C puisse accéder à une bibliothèque écrite en C, mais le client Python illustre qu'une bibliothèque écrite en C peut servir des clients d'autres langages.

Bibliothèques statiques ou dynamiques

Les systèmes Linux disposent de deux types de bibliothèques :

  • Une bibliothèque statique (alias archive de bibliothèque) est intégrée à un client compilé statiquement (par exemple, un en C ou Rust) pendant la phase de liaison du processus de compilation. En effet, chaque client obtient sa propre copie de la bibliothèque. Un inconvénient important d'une bibliothèque statique apparaît si la bibliothèque doit être révisée (par exemple, pour corriger un bug), car chaque client de bibliothèque doit être reconnecté à la bibliothèque statique. Une bibliothèque dynamique, décrite ci-après, évite cet inconvénient.
  • Une bibliothèque dynamique (c'est-à-dire partagée) est marquée pendant la phase de liaison d'un programme client compilé de manière statique, mais le programme client et le code de la bibliothèque restent autrement déconnectés jusqu'à l'exécution : le code de la bibliothèque n'est pas intégré au client. Au moment de l'exécution, le chargeur dynamique du système connecte une bibliothèque partagée à un client en cours d'exécution, que le client provienne d'un langage compilé statiquement, tel que C, ou d'un langage compilé dynamiquement, tel que Python. En conséquence, une bibliothèque dynamique peut être mise à jour sans gêner les clients. Enfin, plusieurs clients peuvent partager une seule copie d'une bibliothèque dynamique.

En général, les bibliothèques dynamiques sont préférées aux bibliothèques statiques, même si elles ont un coût en termes de complexité et de performances. Voici comment chaque type de bibliothèque est créé et publié :

  1. Le code source de la bibliothèque est compilé en un ou plusieurs modules objets, qui sont des fichiers binaires pouvant être inclus dans une bibliothèque et liés à des clients exécutables.
  2. Les modules objets sont regroupés dans un seul fichier. Pour une bibliothèque statique, l'extension standard est .a pour « archive ». Pour une bibliothèque dynamique, l'extension est .so pour « objet partagé ». Les deux bibliothèques d'exemples, qui ont la même fonctionnalité, sont publiées sous forme de fichiers libprimes.a (statique) et libshprimes.so (dynamique). Le préfixe lib est utilisé pour les deux types de bibliothèque.
  3. Le fichier de bibliothèque est copié dans un répertoire standard afin que les programmes clients puissent accéder facilement à la bibliothèque. Un emplacement typique pour la bibliothèque, qu'elle soit statique ou dynamique, est /usr/lib ou /usr/local/lib ; d'autres emplacements sont possibles.

Les étapes détaillées pour créer et publier chaque type de bibliothèque seront bientôt disponibles. Cependant, je vais d’abord présenter les fonctions C dans les deux bibliothèques.

Les fonctions de la bibliothèque d'échantillons

Les deux bibliothèques d'exemples sont construites à partir des cinq mêmes fonctions C, dont quatre sont accessibles aux programmes clients. La cinquième fonction, qui est un utilitaire pour l'une des quatre autres, montre comment C prend en charge le masquage d'informations. Le code source de chaque fonction est suffisamment court pour que les fonctions puissent être hébergées dans un seul fichier source, bien que plusieurs fichiers sources (par exemple, un pour chacune des quatre fonctions publiées) soient une option.

Les fonctions de la bibliothèque sont élémentaires et traitent, de diverses manières, les nombres premiers. Toutes les fonctions attendent des valeurs entières non signées (c'est-à-dire non négatives) comme arguments :

  • La fonction is_prime teste si son unique argument est un nombre premier.
  • La fonction are_coprimes vérifie si ses deux arguments ont un plus grand diviseur commun (pgcd) de 1, qui définit les co-primes.
  • La fonction prime_factors liste les facteurs premiers de son argument.
  • La fonction goldbach attend une valeur entière paire de 4 ou plus, listant la somme des deux nombres premiers de cet argument ; il peut y avoir plusieurs paires de sommation. La fonction porte le nom du mathématicien du XVIIIe siècle Christian Goldbach, dont la conjecture selon laquelle tout entier pair supérieur à deux est la somme de deux nombres premiers reste l'un des plus anciens problèmes non résolus de la théorie des nombres.

La fonction utilitaire gcd réside dans les fichiers de bibliothèque déployés, mais cette fonction n'est pas accessible en dehors du fichier qui la contient ; par conséquent, un client de bibliothèque ne peut pas invoquer directement la fonction gcd. Un examen plus approfondi des fonctions C clarifie ce point.

Seules les fonctions du fichier primes.c peuvent invoquer gcd, et seule la fonction are_coprimes le fait. Lorsque les bibliothèques statiques et dynamiques sont construites et publiées, d'autres programmes peuvent appeler une fonction extern telle que are_coprimes mais pas la fonction static pgcd. La classe de stockage static cache ainsi la fonction gcd aux clients de la bibliothèque en limitant la portée de la fonction aux autres fonctions de la bibliothèque.

Les fonctions autres que gcd dans le fichier primes.c n'ont pas besoin de spécifier une classe de stockage, qui serait par défaut extern. Cependant, il est courant dans les bibliothèques de rendre explicite le extern.

C fait la distinction entre les définitions de fonctions et les déclarations, ce qui est important pour les bibliothèques. Commençons par les définitions. C n'a que des fonctions nommées, et chaque fonction est définie avec :

  • Un nom unique. Deux fonctions d'un programme ne peuvent pas porter le même nom.
  • Une liste d'arguments, qui peut être vide. Les arguments sont tapés.
  • Soit un type de valeur de retour (par exemple, int pour un entier signé 32 bits) ou void si aucune valeur n'est renvoyée.
  • Un corps entouré d’accolades. Dans un exemple artificiel, le corps pourrait être vide.

Chaque fonction d'un programme doit être définie exactement une fois.

Voici la définition complète de la fonction de bibliothèque are_coprimes :

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

La fonction renvoie une valeur booléenne (soit 0 pour faux, soit 1 pour vrai), selon que les deux arguments entiers ont un plus grand diviseur commun de 1. La fonction utilitaire gcd calcule le plus grand diviseur commun d'un entier. arguments n1 et n2.

Une déclaration de fonction, contrairement à une définition, n'a pas de corps :

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

La déclaration se termine par un point-virgule après la liste d'arguments ; il n’y a pas d’accolades entourant un corps. Une fonction peut être déclarée plusieurs fois dans un programme.

Pourquoi les déclarations sont-elles nécessaires ? En C, une fonction appelée doit être visible par son appelant. Il existe différentes manières de fournir une telle visibilité, en fonction du niveau de difficulté du compilateur. Un moyen sûr consiste à définir la fonction appelée au-dessus de son appelant lorsque les deux résident dans le même fichier :

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

La définition de la fonction f pourrait être déplacée sous l'appel de la fonction g si f était déclaré au-dessus de l'appel :

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

Mais que se passe-t-il si la fonction appelée réside dans un fichier différent de celui de son appelant ? Comment les fonctions définies dans un fichier sont-elles rendues visibles dans un autre fichier, étant donné que chaque fonction doit être définie exactement une fois dans un programme ?

Ce problème affecte les bibliothèques, qu'elles soient statiques ou dynamiques. Par exemple, les fonctions des deux bibliothèques primes sont définies dans le fichier source primes.c, dont des copies binaires se trouvent dans chaque bibliothèque ; mais ces fonctions définies doivent être visibles par un client de bibliothèque en C, qui est un programme distinct avec son(ses) propre(s) fichier(s) source(s).

Fournir une visibilité sur les fichiers est ce que les déclarations de fonctions peuvent faire. Pour les exemples "primes", il existe un fichier d'en-tête nommé primes.h qui déclare les quatre fonctions à rendre visibles aux clients de la bibliothèque en C :

/** 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);

Ces déclarations servent d'interface en spécifiant la syntaxe d'invocation pour chaque fonction.

Pour la commodité du client, le fichier texte primes.h pourrait être stocké dans un répertoire sur le chemin de recherche du compilateur C. Les emplacements typiques sont /usr/include et /usr/local/include. Un client C #include ce fichier d'en-tête en haut du code source du client. (Un fichier d'en-tête est ainsi importé dans le "head" d'un autre fichier source.) Les fichiers d'en-tête C servent également d'entrée aux utilitaires (par exemple, bindgen de Rust) qui permettent aux clients dans d'autres langages d'accéder à un fichier C. bibliothèque.

En résumé, une fonction de bibliothèque est définie exactement une fois mais déclarée partout où cela est nécessaire ; tout client de bibliothèque en C a besoin de la déclaration. Un fichier d'en-tête doit contenir des déclarations de fonctions, mais pas des définitions de fonctions. Si un fichier d'en-tête contenait des définitions, le fichier pourrait être inclus plusieurs fois dans un programme C, enfreignant ainsi la règle selon laquelle une fonction doit être définie exactement une fois dans un programme C.

Le code source de la bibliothèque

Vous trouverez ci-dessous le code source de deux bibliothèques. Ce code, le fichier d'en-tête et les deux exemples de clients sont disponibles sur mon site Web.

Les fonctions de la bibliothèque

#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 */
    }
  }
}

Ces fonctions servent de grain au moulin de la bibliothèque. Les deux bibliothèques dérivent exactement du même code source, et le fichier d'en-tête primes.h est l'interface C des deux bibliothèques.

Construire les bibliothèques

Les étapes de création et de publication de bibliothèques statiques et dynamiques diffèrent sur quelques détails. Seules trois étapes sont requises pour la bibliothèque statique et seulement deux autres pour la bibliothèque dynamique. Les étapes supplémentaires dans la création de la bibliothèque dynamique reflètent la flexibilité supplémentaire de l'approche dynamique. Commençons par la bibliothèque statique.

Le fichier source de la bibliothèque primes.c est compilé dans un module objet. Voici la commande, avec le signe de pourcentage comme invite du système (les doubles signes pointus introduisent mes commentaires) :

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

Cela produit le fichier binaire primes.o, le module objet. L'indicateur -c signifie compiler uniquement.

L'étape suivante consiste à archiver le(s) module(s) objet à l'aide de l'utilitaire Linux ar :

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

Les trois indicateurs -cvq sont l'abréviation de "créer", "verbeux" et "ajout rapide" (au cas où de nouveaux fichiers doivent être ajoutés à une archive). Rappelons que le préfixe lib est standard, mais le nom de la bibliothèque est arbitraire. Bien entendu, le nom de fichier d'une bibliothèque doit être unique pour éviter les conflits.

L'archive est prête à être publiée :

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

La bibliothèque statique est désormais accessible aux clients, dont des exemples sont à venir. (Le sudo est inclus pour garantir les droits d'accès corrects pour copier un fichier dans /usr/local/lib.)

La bibliothèque dynamique nécessite également un ou plusieurs modules objets pour l'empaquetage :

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

L'indicateur ajouté -fpic demande au compilateur de générer du code indépendant de la position, qui est un module binaire qui n'a pas besoin d'être chargé dans un emplacement mémoire fixe. Une telle flexibilité est essentielle dans un système composé de plusieurs bibliothèques dynamiques. Le module objet résultant est légèrement plus grand que celui généré pour la bibliothèque statique.

Voici la commande pour créer le fichier de bibliothèque unique à partir du(des) module(s) objet :

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

Le flag -shared indique que la bibliothèque est partagée (dynamique) plutôt que statique. L'indicateur -Wl introduit une liste d'options du compilateur, dont la première définit le soname de la bibliothèque dynamique, qui est requis. Le soname spécifie d'abord le nom logique de la bibliothèque (libshprimes.so) puis, après l'indicateur -o, le nom de fichier physique de la bibliothèque (libshprimes.so.1). L'objectif est de conserver le nom logique constant tout en permettant au nom physique du fichier de changer avec les nouvelles versions. Dans cet exemple, le 1 à la fin du nom de fichier physique libshprimes.so.1 représente la première version de la bibliothèque. Les noms de fichiers logiques et physiques peuvent être les mêmes, mais la meilleure pratique consiste à avoir des noms distincts. Un client accède à la bibliothèque via son nom logique (dans ce cas, libshprimes.so), comme je le préciserai sous peu.

L'étape suivante consiste à rendre la bibliothèque partagée facilement accessible aux clients en la copiant dans le répertoire approprié ; par exemple, /usr/local/lib encore une fois :

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

Un lien symbolique est désormais établi entre le nom logique de la bibliothèque partagée (libshprimes.so) et son nom physique complet de fichier (/usr/local/lib/libshprimes.so.1). Il est plus simple de donner la commande avec /usr/local/lib comme répertoire de travail :

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

Le nom logique libshprimes.so ne devrait pas changer, mais la cible du lien symbolique (libshrimes.so.1) peut être mise à jour si nécessaire pour les nouvelles implémentations de bibliothèques qui corrigent les bogues. , améliorer les performances, etc.

La dernière étape (une étape de précaution) consiste à appeler l'utilitaire ldconfig, qui configure le chargeur dynamique du système. Cette configuration garantit que le chargeur trouvera la bibliothèque nouvellement publiée :

% sudo ldconfig ## step 5 dynamic

La bibliothèque dynamique est maintenant prête pour les clients, y compris les deux exemples qui suivent.

Un client de bibliothèque C

L'exemple de client C est le testeur de programme, dont le code source commence par deux directives #include :

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

Les crochets angulaires autour des noms de fichiers indiquent que ces fichiers d'en-tête se trouvent sur le chemin de recherche du compilateur (dans le cas de primes.h, le répertoire /usr/local/include). Sans ce #include, le compilateur se plaindrait de déclarations manquantes pour des fonctions telles que is_prime et prime_factors, qui sont publiées dans les deux bibliothèques. À propos, le code source du programme de test n'a pas besoin d'être modifié du tout pour tester chacune des deux bibliothèques.

En revanche, le fichier source de la bibliothèque (primes.c) s'ouvre avec ces directives #include :

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

Le fichier d'en-tête math.h est requis car la fonction de bibliothèque prime_factors appelle la fonction mathématique sqrt dans la bibliothèque standard libm.so.

Pour référence, voici le code source du programme testeur :

Le programme testeur

#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;
}

Lors de la compilation de tester.c dans un exécutable, la partie délicate est l'ordre des indicateurs de lien. Rappelons que les deux exemples de bibliothèques commencent par le préfixe lib, et chacune a l'extension habituelle : .a pour la bibliothèque statique libprimes.a et .so pour la bibliothèque dynamique libshprimes.so. Dans une spécification de liens, le préfixe lib et l'extension disparaissent. Un indicateur de lien commence par -l (L minuscule) et une commande de compilation peut contenir de nombreux indicateurs de lien. Voici la commande de compilation complète pour le programme testeur, en utilisant la bibliothèque dynamique comme exemple :

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

Le premier indicateur de lien identifie la bibliothèque libshprimes.so et le deuxième indicateur de lien identifie la bibliothèque mathématique standard libm.so.

L'éditeur de liens est paresseux, ce qui signifie que l'ordre des indicateurs de lien est important. Par exemple, inverser l’ordre des spécifications de lien génère une erreur de compilation :

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

L'indicateur qui renvoie à libm.so vient en premier, mais aucune fonction de cette bibliothèque n'est invoquée explicitement dans le programme testeur ; par conséquent, l'éditeur de liens n'est pas lié à la bibliothèque math.so. L'appel à la fonction de la bibliothèque sqrt se produit uniquement dans la fonction prime_factors qui est désormais contenue dans la bibliothèque libshprimes.so. L'erreur résultante lors de la compilation du programme de test est :

primes.c: undefined reference to 'sqrt'

En conséquence, l'ordre des indicateurs de lien doit informer l'éditeur de liens que la fonction sqrt est nécessaire :

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

L'éditeur de liens récupère l'appel à la fonction de bibliothèque sqrt dans la bibliothèque libshprimes.so et, par conséquent, établit le lien approprié vers la bibliothèque mathématique libm.so. Il existe une option de liaison plus compliquée qui prend en charge l'un ou l'autre ordre de lien-indicateur ; dans ce cas, cependant, le moyen le plus simple consiste à organiser les indicateurs de lien de manière appropriée.

Voici quelques résultats d’une exécution du client testeur :

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
...

Pour la fonction goldbach, même une valeur paire relativement petite (par exemple, 18) peut avoir plusieurs paires de nombres premiers qui lui font la somme (dans ce cas, 5+13 et 7+11). De telles paires premières multiples font partie des facteurs qui compliquent une tentative de preuve de la conjecture de Goldbach.

Conclusion avec un client Python

Python, contrairement à C, n'est pas un langage compilé de manière statique, ce qui signifie que l'exemple de client Python doit accéder à la version dynamique plutôt qu'à la version statique de la bibliothèque primes. Pour ce faire, Python dispose de différents modules (standards et tiers) qui prennent en charge une interface de fonction étrangère (FFI), qui permet à un programme écrit dans un langage d'invoquer des fonctions écrites dans un autre. Python ctypes est un FFI standard et relativement simple qui permet au code Python d'appeler des fonctions C.

Tout FFI présente des défis car il est peu probable que les langages d’interface aient exactement les mêmes types de données. Par exemple, la bibliothèque primes utilise le type C unsigned int, que Python n'a pas ; le FFI ctypes mappe un unsigned int C à un int Python. Sur les quatre fonctions C extern publiées dans la bibliothèque primes, deux se comportent mieux en Python avec une configuration ctypes explicite.

Les fonctions C prime_factors et goldbach ont void au lieu d'un type de retour, mais ctypes remplace par défaut le C void avec le int Python. Lorsqu'elles sont appelées à partir du code Python, les deux fonctions C renvoient alors une valeur entière aléatoire (donc dénuée de sens) de la pile. Cependant, ctypes peut être configuré pour que les fonctions renvoient None (le type nul de Python) à la place. Voici la configuration de la fonction prime_factors :

primes.prime_factors.restype = None

Une instruction similaire gère la fonction goldbach.

La session interactive ci-dessous (en Python 3) montre que l'interface entre un client Python et la bibliothèque primes est simple :

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

Les fonctions de la bibliothèque primes utilisent uniquement un type de données simple, unsigned int. Si cette bibliothèque C utilisait des types compliqués tels que des structures, et si des pointeurs vers des structures étaient transmis et renvoyés par les fonctions de la bibliothèque, alors un FFI plus puissant que ctypes pourrait être meilleur pour une interface fluide entre Python et C. Néanmoins, l'exemple ctypes montre qu'un client Python peut utiliser une bibliothèque écrite en C. En effet, la populaire bibliothèque NumPy pour le calcul scientifique est écrite en C puis exposée dans une API Python de haut niveau.

La bibliothèque simple primes et la bibliothèque avancée NumPy soulignent que le C reste la lingua franca parmi les langages de programmation. Presque tous les langages peuvent communiquer avec C et, via C, avec tout autre langage qui communique avec C. Python parle facilement avec C et, comme autre exemple, Java pourrait faire de même lorsque le Projet Panama deviendra une alternative à Java Native Interface (JNI). ).

Articles connexes: