C pour les programmeurs Python – Real Python

By | janvier 20, 2021

Formation Python

Le but de ce didacticiel est de familiariser un programmeur Python expérimenté avec les bases du langage C et son utilisation dans le code source CPython. Cela suppose que vous avez déjà une compréhension intermédiaire de la syntaxe Python.

Cela dit, C est un langage assez limité et la plupart de son utilisation dans CPython relève d'un petit ensemble de règles de syntaxe. Arriver au point où vous comprenez le code est une étape beaucoup plus petite que d'être capable d'écrire C efficacement. Ce tutoriel vise le premier objectif mais pas le second.

L'une des premières choses qui se démarque comme une grande différence entre Python et C est le préprocesseur C. Vous examinerez cela en premier.

Le préprocesseur C

Le préprocesseur, comme son nom l'indique, est exécuté sur vos fichiers source avant l'exécution du compilateur. Il a des capacités très limitées, mais vous pouvez les utiliser à grand avantage dans la construction de programmes C.

Le préprocesseur produit un nouveau fichier, qui est ce que le compilateur va réellement traiter. Toutes les commandes du préprocesseur démarrent au début d'une ligne, avec un # symbole comme premier caractère non blanc.

Le but principal du préprocesseur est de faire une substitution de texte dans le fichier source, mais il fera également du code conditionnel de base avec #si ou des déclarations similaires.

Vous allez commencer par la directive de préprocesseur la plus fréquente: #comprendre.

#comprendre

#comprendre est utilisé pour extraire le contenu d'un fichier dans le fichier source actuel. Il n'y a rien de sophistiqué dans #comprendre. Il lit un fichier à partir du système de fichiers, exécute le préprocesseur sur ce fichier et place les résultats dans le fichier de sortie. Ceci est fait récursivement pour chaque #comprendre directif.

Par exemple, si vous regardez les CPython Modules / _multiprocessing / semaphore.c fichier, puis vers le haut, vous verrez la ligne suivante:

#comprendre "multiprocessing.h"

Cela indique au préprocesseur d'extraire tout le contenu de multiprocessing.h et placez-les dans le fichier de sortie à cette position.

Vous remarquerez deux formes différentes pour le #comprendre déclaration. L'un d'eux utilise des guillemets ("") pour spécifier le nom du fichier d'inclusion, et l'autre utilise des chevrons (<>). La différence vient des chemins à rechercher lors de la recherche du fichier sur le système de fichiers.

Si tu utilises <> pour le nom de fichier, le préprocesseur examinera uniquement les fichiers d'inclusion système. L'utilisation de guillemets autour du nom de fichier forcera le préprocesseur à chercher d'abord dans le répertoire local, puis à revenir aux répertoires système.

#définir

#définir vous permet de faire une substitution de texte simple et joue également dans le #si directives que vous verrez ci-dessous.

Dans sa forme la plus élémentaire, #définir vous permet de définir un nouveau symbole qui est remplacé par une chaîne de texte dans la sortie du préprocesseur.

Continuer dans semphore.c, vous trouverez cette ligne:

Cela indique au préprocesseur de remplacer chaque instance de SEM_FAILED en dessous de ce point avec la chaîne littérale NUL avant que le code ne soit envoyé au compilateur.

#définir les éléments peuvent également prendre des paramètres comme dans cette version spécifique à Windows de SEM_CREATE:

#define SEM_CREATE (nom, val, max) CreateSemaphore (NULL, val, max, NULL)

Dans ce cas, le préprocesseur attendra SEM_CREATE () pour ressembler à un appel de fonction et avoir trois paramètres. Ceci est généralement appelé un macro. Il remplacera directement le texte des trois paramètres dans le code de sortie.

Par exemple, à la ligne 460 de semphore.c, la SEM_CREATE la macro est utilisée comme ceci:

manipuler = SEM_CREATE(Nom, valeur, max);

Lorsque vous compilez pour Windows, cette macro sera développée pour que la ligne ressemble à ceci:

manipuler = CréerSémaphore(NUL, valeur, max, NUL);

Dans une section ultérieure, vous verrez comment cette macro est définie différemment sur Windows et d'autres systèmes d'exploitation.

#undef

Cette directive efface toute définition de préprocesseur précédente de #définir. Cela permet d'avoir un #définir en vigueur pour une partie seulement d'un fichier.

#si

Le préprocesseur autorise également les instructions conditionnelles, vous permettant d'inclure ou d'exclure des sections de texte en fonction de certaines conditions. Les instructions conditionnelles sont fermées avec le #fin si directive et peut également utiliser #elif et #autre pour des réglages précis.

Il existe trois formes de base de #si que vous verrez dans la source CPython:

  1. #ifdef inclut le bloc de texte suivant si la macro spécifiée est définie. Vous pouvez également le voir écrit comme #if défini ().
  2. #ifndef inclut le bloc de texte suivant si la macro spécifiée est ne pas défini.
  3. #si inclut le bloc de texte suivant si la macro est définie et il évalue à Vrai.

Notez l'utilisation de "texte" au lieu de "code" pour décrire ce qui est inclus ou exclu du fichier. Le préprocesseur ne sait rien de la syntaxe C et ne se soucie pas du texte spécifié.

#pragma

Les pragmas sont des instructions ou des astuces pour le compilateur. En général, vous pouvez les ignorer lors de la lecture du code car ils traitent généralement de la façon dont le code est compilé, et non de la façon dont le code s'exécute.

#Erreur

Finalement, #Erreur affiche un message et provoque l'arrêt du préprocesseur. Encore une fois, vous pouvez les ignorer en toute sécurité pour lire le code source CPython.

Syntaxe C de base pour les programmeurs Python

Cette section ne couvrira pas tout aspects de C, il ne vise pas non plus à vous apprendre à écrire C. Il se concentrera sur les aspects de C qui sont différents ou déroutants pour les développeurs Python la première fois qu'ils les voient.

Général

Contrairement à Python, les espaces n’est pas important pour le compilateur C. Le compilateur ne se soucie pas si vous divisez des instructions sur plusieurs lignes ou si vous bloquez tout votre programme en une seule très longue ligne. En effet, il utilise des délimiteurs pour toutes les instructions et tous les blocs.

Il existe bien sûr des règles très spécifiques pour l'analyseur, mais en général, vous serez en mesure de comprendre la source CPython en sachant simplement que chaque instruction se termine par un point-virgule (;), et tous les blocs de code sont entourés d'accolades ().

L'exception à cette règle est que si un bloc n'a qu'une seule instruction, les accolades peuvent être omises.

Toutes les variables en C doivent être déclaré, ce qui signifie qu'il doit y avoir une seule déclaration indiquant le type de cette variable. Notez que, contrairement à Python, le type de données qu'une seule variable peut contenir ne peut pas changer.

Voici quelques exemples:

/ * Les commentaires sont inclus entre slash-astérisque et astérisque-slash * /
/ * Ce style de commentaire peut s'étendre sur plusieurs lignes -
            donc cette partie est encore un commentaire. * /

// Les commentaires peuvent également venir après deux barres obliques
// Ce type de commentaire ne va que jusqu'à la fin de la ligne, donc nouveau
// les lignes doivent commencer par des doubles barres obliques (//).

int X = 0; // Déclare x comme étant de type 'int' et l'initialise à 0

si (X == 0) 
    // Ceci est un bloc de code
    int y = 1;  // y n'est qu'un nom de variable valide jusqu'à la fermeture
    // Plus de déclarations ici
    printf("x est% d y est% d\n ", X, y);
}

// Les blocs sur une seule ligne ne nécessitent pas de parenthèses
si (X == 13)
    printf("x est 13!\n ");
printf("passé le bloc if\n ");

En général, vous verrez que le code CPython est formaté de manière très claire et reste généralement fidèle à un seul style dans un module donné.

si Déclarations

En C, si fonctionne généralement comme il le fait en Python. Si la condition est vraie, le bloc suivant est exécuté. le autre et sinon si la syntaxe doit être suffisamment familière aux programmeurs Python. Notez que C si les déclarations n’ont pas besoin fin si car les blocs sont délimités par .

Il y a un raccourci en C pour faire court siautre déclarations appelées opérateur ternaire:

état ? true_result : false_result

Vous pouvez le trouver dans sémaphore.c où, pour Windows, il définit une macro pour SEM_CLOSE ():

#define SEM_CLOSE (sem) (CloseHandle (sem)? 0: -1)

La valeur de retour de cette macro sera 0 si la fonction Fermer la poignée () Retour vrai et -1 autrement.

commutateur Déclarations

Contrairement à Python, C prend également en charge commutateur. En utilisant commutateur peut être considéré comme un raccourci pour sisinon si Chaînes. Cet exemple est de sémaphore.c:

commutateur (WaitForSingleObjectEx(manipuler, 0, FAUX)) 
Cas WAIT_OBJECT_0:
    si (!LibérationSémaphore(manipuler, 1, &précédent))
        revenir MP_STANDARD_ERROR;
    *valeur = précédent + 1;
    revenir 0;
Cas WAIT_TIMEOUT:
    *valeur = 0;
    revenir 0;
défaut:
    revenir MP_STANDARD_ERROR;

Cela effectue un basculement sur la valeur de retour de WaitForSingleObjectEx (). Si la valeur est WAIT_OBJECT_0, puis le premier bloc est exécuté. le WAIT_TIMEOUT value aboutit au second bloc, et tout le reste correspond au défaut bloquer.

Notez que la valeur testée, dans ce cas la valeur de retour de WaitForSingleObjectEx (), doit être une valeur intégrale ou un type énuméré, et chaque Cas doit être une valeur constante.

Boucles

Il existe trois structures en boucle en C:

  1. pour boucles
  2. tandis que boucles
  3. fairetandis que boucles

pour les boucles ont une syntaxe assez différente de Python:

pour ( <initialisation>; <état>; <incrément>) 
    <code à être bouclé plus de>

En plus du code à exécuter dans la boucle, il existe trois blocs de code qui contrôlent le pour boucle:

  1. le section s'exécute exactement une fois au démarrage de la boucle. Il est généralement utilisé pour définir un compteur de boucle sur une valeur initiale (et éventuellement pour déclarer le compteur de boucle).

  2. le le code s'exécute immédiatement après chaque passage dans le bloc principal de la boucle. Traditionnellement, cela incrémentera le compteur de boucle.

  3. Finalement, le court après le . La valeur de retour de ce code sera évaluée et la boucle se rompt lorsque cette condition retourne false.

Voici un exemple de Modules / sha512module.c:

pour (je = 0; je < 8; ++je) 
    S[[[[je] = sha_info->digérer[[[[je];

Cette boucle fonctionnera 8 fois, avec je incrémentation de 0 à sept, et se terminera lorsque la condition est vérifiée et je est 8.

tandis que les boucles sont pratiquement identiques à leurs homologues Python. le fairetandis que la syntaxe est cependant un peu différente. La condition sur un fairetandis que la boucle n’est pas vérifiée tant que après le corps de la boucle est exécuté pour la première fois.

Il existe de nombreux exemples de pour boucles et tandis que boucles dans la base de code CPython, mais fairetandis que est inutilisé.

Les fonctions

La syntaxe des fonctions en C est similaire à celle de Python, avec l'ajout que le type de retour et les types de paramètres doivent être spécifiés. La syntaxe C ressemble à ceci:

<return_type> nom_fonction(<paramètres>) 
    <corps_fonction>

Le type de retour peut être n'importe quel type valide en C, y compris des types intégrés comme int et double ainsi que des types personnalisés comme PyObject, comme dans cet exemple de sémaphore.c:

statique PyObject *
semlock_release(SemLockObject *soi, PyObject *args)

    <déclarations de fonction corps ici>

Ici, vous voyez quelques fonctionnalités spécifiques à C en jeu. Tout d’abord, rappelez-vous que les espaces n’ont pas d’importance. Une grande partie du code source CPython place le type de retour d'une fonction sur la ligne au-dessus du reste de la déclaration de fonction. C'est le PyObject * partie. Vous examinerez de plus près l’utilisation de * un peu plus tard, mais pour l’instant, il est important de savoir qu’il existe plusieurs modificateurs que vous pouvez placer sur les fonctions et les variables.

statique est l'un de ces modificateurs. Il existe des règles complexes régissant le fonctionnement des modificateurs. Par exemple, le statique modificateur ici signifie quelque chose de très différent de celui que vous aviez placé devant une déclaration de variable.

Heureusement, vous pouvez généralement ignorer ces modificateurs tout en essayant de lire et de comprendre le code source CPython.

La liste des paramètres des fonctions est une liste de variables séparées par des virgules, similaire à ce que vous utilisez en Python. Encore une fois, C nécessite des types spécifiques pour chaque paramètre, donc SemLockObject * self dit que le premier paramètre est un pointeur vers un SemLockObject et s'appelle soi. Notez que tous les paramètres en C sont positionnels.

Voyons ce que signifie la partie "pointeur" de cette déclaration.

Pour donner un peu de contexte, les paramètres qui sont passés aux fonctions C sont tous passé par valeur, ce qui signifie que la fonction opère sur une copie de la valeur et non sur la valeur d'origine dans la fonction appelante. Pour contourner ce problème, les fonctions transmettront fréquemment l'adresse de certaines données que la fonction peut modifier.

Ces adresses sont appelées pointeurs et avoir des types, donc int * est un pointeur vers une valeur entière et est d'un type différent de double *, qui est un pointeur vers un nombre à virgule flottante double précision.

Pointeurs

Comme mentionné ci-dessus, les pointeurs sont des variables qui contiennent l'adresse d'une valeur. Ceux-ci sont fréquemment utilisés en C, comme le montre cet exemple:

statique PyObject *
semlock_release(SemLockObject *soi, PyObject *args)

    <déclarations de fonction corps ici>

Ici le soi le paramètre contiendra l'adresse de, ou un pointeur vers, une SemLockObject valeur. Notez également que la fonction renverra un pointeur vers un PyObject valeur.

Il y a une valeur spéciale en C appelée NUL cela indique qu'un pointeur ne pointe vers rien. Vous verrez des pointeurs attribués à NUL et vérifié contre NUL dans toute la source CPython. Ceci est important car il y a très peu de limitations quant aux valeurs qu'un pointeur peut avoir, et l'accès à un emplacement mémoire qui ne fait pas partie de votre programme peut provoquer un comportement très étrange.

En revanche, si vous essayez d'accéder à la mémoire à NUL, alors votre programme se fermera immédiatement. Cela peut ne pas sembler mieux, mais il est généralement plus facile de détecter un bogue de mémoire si NUL est accessible que si une adresse mémoire aléatoire est modifiée.

Cordes

C n'a pas de type chaîne. Il existe une convention autour de laquelle de nombreuses fonctions de bibliothèque standard sont écrites, mais il n'y a pas de type réel. Au contraire, les chaînes en C sont stockées sous forme de tableaux de carboniser (pour ASCII) ou wchar (pour Unicode), dont chacune contient un seul caractère. Les chaînes sont marquées d'un terminateur nul, qui a une valeur 0 et est généralement affiché dans le code comme \ 0.

Opérations de base sur les chaînes comme strlen () comptez sur ce terminateur nul pour marquer la fin de la chaîne.

Étant donné que les chaînes ne sont que des tableaux de valeurs, elles ne peuvent pas être directement copiées ou comparées. La bibliothèque standard a le strcpy () et strcmp () fonctions (et leurs wchar cousins) pour avoir effectué ces opérations et plus encore.

Structs

Votre dernière étape de cette mini-visite de C est de savoir comment créer de nouveaux types en C: structs. le struct mot-clé vous permet de regrouper un ensemble de types de données différents dans un nouveau type de données personnalisé:

struct <nom_struct> 
    <type> <nom de membre>;
    <type> <nom de membre>;
    ...
;

Cet exemple partiel de Modules / arraymodule.c montre un struct déclaration:

struct arraydescr 
    carboniser code de type;
    int taille de l'article;
    ...
;

Cela crée un nouveau type de données appelé arraydescr qui compte de nombreux membres, dont les deux premiers sont code de type char Et un int itemsize.

Les structures seront fréquemment utilisées dans le cadre d'un typedef, qui fournit un simple alias pour le nom. Dans l'exemple ci-dessus, toutes les variables du nouveau type doivent être déclarées avec le nom complet struct arraydescr x;.

Vous verrez fréquemment une syntaxe comme celle-ci:

typedef struct 
    PyObject_HEAD
    SEM_HANDLE manipuler;
    non signé longue last_tid;
    int compter;
    int Valeur max;
    int gentil;
    carboniser *Nom;
 SemLockObject;

Cela crée un nouveau type de structure personnalisé et lui donne le nom SemLockObject. Pour déclarer une variable de ce type, vous pouvez simplement utiliser l'alias SemLockObject x;.

Conclusion

Cela conclut votre visite rapide de la syntaxe C. Bien que cette description efface à peine la surface du langage C, vous avez maintenant des connaissances suffisantes pour lire et comprendre le code source CPython.

Dans ce didacticiel, vous avez appris:

  • Qu'est-ce que Préprocesseur C est et quel rôle il joue dans la construction de programmes C
  • Comment vous pouvez utiliser Directives du préprocesseur pour manipuler les fichiers source
  • Comment Syntaxe C se compare à Syntaxe Python
  • Comment créer boucles, les fonctions, cordeset d'autres fonctionnalités de C

Maintenant que vous êtes familiarisé avec C, vous pouvez approfondir vos connaissances sur le fonctionnement interne de Python en explorant le code source de CPython. Joyeux Python!

[ad_2]