Язык C для программистов на Python
Если вы уже владеете синтаксисом Python, самое время познакомиться с основами языка C и посмотреть, как он используется в исходном коде CPython. Обсудить Перевод публикуется с сокращениями, автор оригинальной статьи Jim Anderson. Цель этого урока – познакомить опытного программиста Python с основами языка C и тем, как он используется в исходном коде CPython. Это предполагает, что у вас уже есть промежуточное понимание синтаксиса Python. C – довольно ограниченный язык, и большая часть его использования в CPython подпадает под небольшой набор синтаксических правил. Добраться до точки, где вы понимаете код, гораздо проще, чем уметь писать эффективные программы. Туториал нацелен больше на первую задачу. Здесь вы узнаете: Перейдем к рассмотрению препроцессора C. Препроцессор запускается на ваших исходных текстах до запуска компилятора. Он имеет очень ограниченные возможности, но ими можно пользоваться при построении программ на языке C. Препроцессор создает новый файл, который позже будет обрабатывать компилятор. Все команды препроцессора начинаются с символа «#» в начале строки. Основная цель препроцессора – выполнить подстановку текста в исходнике, но он также будет выполнять базовый код с оператором if и аналогичными ему. Начнем рассмотрение с самой частой директивы препроцессора: Например, если вы посмотрите на файл CPython Modules/_multiprocessing/semaphore.c, то в верхней части увидите следующую строку: Это дает задачу препроцессору взять все содержимое multiprocessing.h и поместить его в выходной файл на указанную позицию. Вы могли заметить две разные формы нотации оператора #include. Одна из них использует кавычки («») для указания имени включаемого файла, а другая – угловые скобки (<>). Разница заключается в том, по какому адресу искать необходимый файл в файловой системе. Если используются <>, то препроцессор будет смотреть только на системные включаемые файлы. Использование кавычек вокруг имени заставит препроцессор сначала заглянуть в локальный каталог, а потом вернуться к системным. В самой простой ситуации Вернемся к semphore.c и найдем такую строку: Это заставляет препроцессор заменить каждый экземпляр Определенные с помощью #define элементы также могут принимать параметры, как в данном случае с Здесь препроцессор будет ожидать, что Например, в строке 460 semphore.c макрос После обработки препроцессором для Windows строка будет выглядеть следующим образом: Далее разберемся, как этот макрос определяется в различных ОС. Директива стирает любое предыдущее определение препроцессора из Препроцессор может использовать условные операторы, позволяющие включать или исключать куски текста на основе определенных условий. Условные операторы завершаются директивой Есть три основные формы #if, которые вы могли встречать в исходнике CPython: Обратите внимание на использование «текста» вместо «кода» для описания того, что включено/исключено из файла. Препроцессор ничего не знает о синтаксисе языка С и его не волнует, что там указано. Это инструкция или подсказка компилятору. Обычно можно игнорировать их при чтении кода, поскольку зачастую они имеют дело с процессом компиляции, а не с выполнением. Здесь не будут охвачены все аспекты языка программирования, и вы не научитесь писать код лучше, но будет уделено внимание аспектам C, которые отличаются или сбивают с толку разработчиков Python при первом знакомстве. В отличие от Python, для компилятора языка C пробелы не важны. Ему все равно, разделите ли вы операторы по строкам или затолкаете всю программу в одну очень длинную строку. Это происходит потому, что он использует разделители для всех операторов и блоков. Есть, конечно, очень специфические правила для парсера, но в целом вы сможете понять исходник CPython, просто зная, что каждый оператор заканчивается символом (;), а все блоки кода окружены фигурными скобками ({}). Исключение из этого правила – если блок имеет только один оператор, то фигурные скобки могут быть опущены. Все переменные в C должны быть объявлены, то есть должен быть один оператор, указывающий тип этой переменной. Обратите внимание, что в отличие от Python, тип переменной в C не может измениться. Вот несколько примеров: Код CPython хорошо отформатирован и придерживается одного стиля со всем проектом. В C Как и в других языках, в C есть сокращение для if … else – тернарный оператор: Вы можете найти его в semaphore.c, где определяется макрос для Возвращаемое значение этого макроса будет равно 0, если функция Использование Если значение равно В C есть 3 вида циклов: Синтаксис циклов for отличается от Python: Существует три блока кода, управляющие циклом: Пример из Modules/sha512module.c: Этот цикл будет выполняться 8 раз с шагом i от 0 до 7 и завершится на i=8. Циклы while похожи на Python-аналоги, но синтаксис do … while немного отличается – условие не проверяется до тех пор, пока тело цикла не будет выполнено в первый раз. Синтаксис функций в C также похож на Python-овский, с тем отличием, что должен быть указан тип возвращаемого значения и типы параметров. Выглядит он следующим образом: Возвращаемый тип может быть любым, включая встроенные типы, такие как int и double, а также пользовательские, вроде PyObject, как из примера ниже: Здесь видим несколько специфичных для C особенностей. Помните, что пробелы не имеют значения, и большая часть кода CPython хранит возвращаемый тип функции в строке над остальной частью объявления функции. Теперь рассмотрим странный код с PyObject *. Список параметров для функций представляет собой разделенный запятыми список переменных, подобный тому, что вы используете в Python. C требует определенных типов для каждого параметра, поэтому SemLockObject *self говорит, что первый параметр является указателем на SemLockObject и называется self. Чтобы дать некоторый контекст, все передаваемые функциям C параметры передаются по значению, т. е. функция работает с копией, а не с исходным значением. Чтобы обойти это, функции часто передают адрес данных, которые функция может изменить. Эти адреса называются указателями и имеют типы, поэтому int* является указателем на целочисленное значение и имеет другой тип, нежели double*, который является указателем на число с плавающей точкой. Как упоминалось выше, указатели – это переменные, содержащие адрес значения. Они часто используются в языке C, как показано в примере: Здесь параметр self будет содержать адрес или указатель на значение SemLockObject. Обратите внимание, что функция вернет указатель на значение PyObject. В языке C есть специальное значение NULL, сообщающее, что указатель ни на что не указывает. Это важно знать, поскольку существует очень мало ограничений относительно того, какие значения может иметь указатель, а доступ к не являющейся частью программы ячейке памяти может сломать весь проект. Если попытаться получить доступ к памяти и наткнуться на NULL, то программа немедленно завершится. При такой ситуации проще вычислить ошибку памяти, чем при изменении случайного адреса. C не имеет строкового типа. Строки хранятся в виде массивов значений char (для ASCII) или wchar (для Unicode), каждый из которых содержит один символ. Из-за этого их нельзя напрямую копировать или сравнивать. Стандартная библиотека имеет функции Ключевое слово struct позволяет сгруппировать набор различных типов данных в новый пользовательский тип: Этот пример из Modules/arraymodule.c частично демонстрирует объявление структуры: Создается новый тип данных arraydescr, который имеет много членов: первые два из них являются char typecode и int itemsize. Часто структуры будут использоваться как часть typedef, который предоставляет алиас для имени. В приведенном выше примере все переменные нового типа должны быть объявлены с указанием полного наименования структуры arraydescr х;. Вы часто будете наблюдать такой синтаксис: Создается новый пользовательский тип с именем SemLockObject. Для объявления переменной такого типа можно просто использовать псевдоним SemLockObject x;. Статья не претендует на звание всеобъемлющего мануала по языку C, но теперь у вас есть достаточные знания, чтобы прочитать и понять исходный код CPython. В этом уроке вы узнали: Теперь, когда вы познакомились с C поближе, можно углубить знания о внутренней работе Python, изучив исходный код CPython. Удачи в обучении! Дополнительный материал:Препроцессор C
#include
.#include
#include
используется для извлечения содержимого некоего файла в текущий. В данном процессе нет ничего сложного – считывается файл, запускается препроцессор и помещает результаты в выходной файл. Это делается рекурсивно для каждой директивы #include.
#include "multiprocessing.h"
#define
#define
позволяет выполнить простую подстановку текста и участвует в манипуляциях с директивой #if
, которые описаны ниже.#define
объявляет новую константу, которая заменяется текстовой строкой в выходных данных препроцессора.
#define SEM_FAILED NULL
SEM_FAILED
перед отправкой кода компилятору.SEM_CREATE
для Windows:
#define SEM_CREATE(name, val, max) CreateSemaphore(NULL, val, max, NULL)
SEM_CREATE()
– функция с тремя принимаемыми параметрами и будет заменять текст в выходном коде. Это называется макросом.SEM_CREATE
используется следующим образом:
handle = SEM_CREATE(name, value, max);
handle = CreateSemaphore(NULL, value, max, NULL);
#undef
#define
, что позволяет использовать #define
только для части файла.#if
#endif
и могут использовать #elif
и #else
для точной настройки.#ifdef <macro>
включает в себя последующий блок текста, если задан указанный макрос. Иногда он выглядит как #if defined(<macro>)
;#ifndef <macro>
включает последующий блок текста, если указанный макрос не определен;#if <macro>
включает последующий блок текста, если макрос определен и имеет значение True.#pragma
#error
#error
выводит на экран сообщение и вызывает препроцессор, для остановки выполнения. Данную директиву тоже можно смело игнорировать при чтении исходника CPython.Базовый синтаксис C для Python-программистов
Основы
/* Комментарии вставляются между символами /* и */ */ /* Этот стиль комментария может включать несколько строк – так что эта часть все еще остается комментарием. */ // Такие комментарии создаются после символов // // Этот тип комментария идет только до конца строки, поэтому новые // строки должны начинаться с «//». int x = 0; // Объявляет x типом int и присваивает ему 0 if (x == 0) { // Это блок кода int y = 1; // Еще немного кода printf("x is %d y is %dn", x, y); } // Однострочные блоки не требуют фигурных скобок if (x == 13) printf("x is 13!n"); printf("past the if blockn");
if
if
работает аналогично Python – если условие истинно, то выполняется следующий блок. Синтаксис else
и else if
также должен быть знаком программистам Python. Обратите внимание, что операторы if
не нуждаются в endif
, поскольку блоки разделяются символом {}.
condition ? true_result : false_result
SEM_CLOSE()
:
#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)
CloseHandle()
возвращает true, иначе 1.switch
switch
можно рассматривать как расширенный вариант if … elseif. Взглянем на пример из semaphore.c:
switch (WaitForSingleObjectEx(handle, 0, FALSE)) { case WAIT_OBJECT_0: if (!ReleaseSemaphore(handle, 1, &previous)) return MP_STANDARD_ERROR; *value = previous + 1; return 0; case WAIT_TIMEOUT: *value = 0; return 0; default: return MP_STANDARD_ERROR; }
WAIT_OBJECT_0
, то выполняется первый блок. Значение WAIT_TIMEOUT
приводит ко второму блоку, а все остальное соответствует блоку по умолчанию.Циклы
for ( <initialization>; <condition>; <increment>) { <code to be looped over> }
<initialization>
выполняется один раз при запуске цикла. Используется для установки счетчика в начальное значение (и, возможно, для объявления счетчика циклов).<increment>
запускается сразу после каждого прохождения через основной блок цикла. Традиционно это приведет к увеличению счетчика циклов.<condition>
выполняется после <increment>. Возвращаемое значение этого кода будет вычислено, и цикл прерывается, когда это условие вернет false.
for (i = 0; i < 8; ++i) { S[i] = sha_info->digest[i]; }
Функции
<return_type> function_name(<parameters>) { <function_body> }
static PyObject * semlock_release(SemLockObject *self, PyObject *args) { <statements of function body here> }
Указатели
static PyObject * semlock_release(SemLockObject *self, PyObject *args) { <statements of function body here> }
Строки
strcpy()
и strcmp()
(и их двойники wchar
) для выполнения этих операций и многого другого.Структуры
struct <struct_name> { <type> <member_name>; <type> <member_name>; ... };
struct arraydescr { char typecode; int itemsize; ... };
typedef struct { PyObject_HEAD SEM_HANDLE handle; unsigned long last_tid; int count; int maxvalue; int kind; char *name; } SemLockObject;
Заключение
- 16 views
- 0 Comment