- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности
Базовый синтаксис C 341
1. #ifdef <макрос> включает следующий блок текста, если указанный макрос определен. Также эта конструкция иногда записывается в виде
#if defined(<макрос>).
2. #ifndef <макрос> включает следующий блок текста, если указанный макрос не определен.
3.#if <макрос> включает следующий блок текста, если макрос определен и его вычисление дает результат True.
Обратите внимание: для описания того, что включается или исключается из файла, используется термин «текст», а не «код». Препроцессор ничего не знает о синтаксисе C, и его не интересует, что собой представляет заданный текст.
#pragma
Директивы #pragma содержат инструкции или рекомендации для компилятора. Как правило, при чтении кода на них можно не обращать внимания, так как обычно они влияют на компиляцию кода, а не на его выполнение.
#error
Наконец, директива #error выводит сообщение и заставляет препроцессор прервать обработку. При чтении исходного кода CPython эти директивы также можно смело игнорировать.
БАЗОВЫЙ СИНТАКСИС C
Этот раздел не охватывает все аспекты языка C, и я не ожидаю, что вы научитесь писать на нем код. Основное внимание здесь уделяется тем частям C, которые могут показаться незнакомыми или непонятными для Pythonразработчиков.
Общие сведения
В отличие от Python, пробельные символы (whitespace) не важны для компилятора C. Компилятор не обратит внимания на то, что одна команда разбита
Книги для программистов: https://t.me/booksforits
342 Приложение. Введение в C для Python-программистов
на несколько строк или же вся программа состоит из одной очень длинной строки. Это объясняется тем, что во всех командах и блоках используются разделители.
Конечно, существуют очень конкретные правила для парсера, но в общем случае для понимания исходного кода CPython достаточно знать, что каждая команда завершается точкой с запятой (;), а все блоки кода заключаются в фигурные скобки ({}).
У этого правила есть исключение: если блок состоит только из одной команды, то фигурные скобки можно опустить.
Все переменные в C должны быть объявлены, то есть в программе должна присутствовать одна команда, в которой указывается тип этой переменной. Обратите внимание: в отличие от Python, тип данных, хранящихся в одной переменной, меняться не может.
Рассмотрим несколько примеров:
/* Комментарии размещаются между символами "косая черта-звездочка" */ /* и "звездочка-косая черта" */ /* Такие комментарии могут занимать несколько строк -
а значит, эта часть все еще является комментарием */
//Комментарии также могут следовать за двумя косыми чертами
//Такие комментарии могут идти только до конца строки, поэтому новые
//строки также должны начинаться с двойной косой черты (//)
int x = 0; // Объявляет x типа 'int' и инициализирует его со значением 0
if (x == 0) {
// Это блок кода
int y = 1; // Имя переменной y действительно только до закрывающей скобки } // Другие команды
printf("x is %d y is %d\n", x, y);
}
// В однострочных блоках фигурные скобки не нужны if (x == 13)
printf("x is 13!\n"); printf("past the if block\n");
Как правило, код CPython очень четко отформатирован, а в пределах модуля обычно применяется единый стиль.
Книги для программистов: https://t.me/booksforits
Базовый синтаксис C 343
Операторы if
ВC оператор if работает в целом так же, как в Python: если условие истинно, то выполняется следующий за ним блок. Синтаксис else и elseif должен быть знаком большинству Python-программистов. Следует помнить, что операторам if в C endif не нужен, потому что блоки ограничиваются фигурными скобками {}.
ВC существует сокращенная запись для коротких команд if … else — так называемый тернарный оператор:
условие ? true_результат : false_результат
Например, такой оператор встречается в файле semaphore.c, где он используется для определения макроса SEM_CLOSE() на Windows:
#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)
Возвращаемое значение этого макроса будет равно 0, если функция CloseHandle() возвращает true, или -1 в противном случае.
ПРИМЕЧАНИЕ
Переменные логического типа поддерживаются и используются в раз ных частях исходного кода CPython, но они не являются частью языка. C интерпретирует бинарные условия по простому правилу: 0 или NULL интерпретируется как false, а все остальное — как true.
Операторы switch
В отличие от Python, C также поддерживает команду 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;
Книги для программистов: https://t.me/booksforits
344 Приложение. Введение в C для Python-программистов
return 0; case WAIT_TIMEOUT:
*value = 0; return 0;
default:
return MP_STANDARD_ERROR;
}
Эта команда осуществляет выбор в зависимости от возвращаемого значения
WaitForSingleObjectEx(). Если значение равно WAIT_OBJECT_0, то выполняется первый блок. При значении WAIT_TIMEOUT выполняется второй блок, а все остальные значения перехватываются блоком default.
Обратите внимание: проверяемое значение (в данном случае значение, возвращаемое WaitForSingleObjectEx()) должно быть целочисленного или перечисляемого типа, а в каждой секции case должна быть указана константа.
Циклы
ВC существует три разновидности циклических конструкций:
1.Циклы for.
2.Циклы while.
3.Циклы do … while.
Синтаксис циклов for заметно отличается от Python:
for ( <инициализация>; <условие>; <приращение>) { <многократно выполняемый код>
}
Кроме многократно выполняемого кода цикл for содержит три блока:
1.Блок <инициализация> выполняется ровно один раз при запуске цикла. Обычно он используется для присваивания начального значения счетчику цикла (и, возможно, объявления счетчика цикла).
2.Блок <приращение> выполняется после каждого прохождения основного блока цикла. Традиционно в нем увеличивается счетчик цикла.
3.Наконец, блок <условие>, который выполняется после блока <приращение>. Вычисляется возвращаемое значение, и цикл прерывается, если условие возвращает false.
Книги для программистов: https://t.me/booksforits
Базовый синтаксис C 345
Пример из файла Modules/sha512module.c:
for (i = 0; i < 8; ++i) {
S[i] = sha_info->digest[i];
}
Цикл выполняется 8 раз, с увеличением i от 0 до 7, и завершается, когда при проверке условия окажется, что значение i достигло 8.
Циклы while практически идентичны своим аналогам в Python, а синтаксис do … while несколько отличается. Условие цикла do ... while проверяется только после того, как цикл будет выполнен в первый раз.
Примеры использования циклов for и while в кодовой базе CPython встречаются достаточно часто, но цикл do ... whilе не используется.
Функции
По синтаксису функций язык C похож на Python, не считая того, что в C типы возвращаемого значения и параметров должны быть заданы явно.
Функция в C выглядит так:
<возвращаемый_тип> имя_функции(<параметры>) { <тело_функции>
}
Возвращаемым типом может быть любой действительный тип C, включая встроенные типы (такие, как int и double), а также специальные типы вроде PyObject, как в следующем примере из semaphore.c:
static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
<команды тела функции>
}
Здесь вы видите несколько особенностей, характерных для C. Прежде всего напомню, что пробельные символы игнорируются. В исходном коде CPython возвращаемый функцией тип очень часто указывается в строке над остальной частью объявления функции (фрагмент PyObject *). Смысл символа * будет описан позднее, а пока достаточно сказать, что к функциям и переменным могут применяться различные модификаторы. static — один из таких модификаторов.
Книги для программистов: https://t.me/booksforits
346 Приложение. Введение в C для Python-программистов
Работой модификаторов управляют довольно сложные правила. Например, смысл модификатора static в данном случае полностью отличается от того, что он означал бы при размещении перед объявлением переменной.
К счастью, когда вы пытаетесь прочитать и понять исходный код CPython, модификаторы обычно можно игнорировать.
Список параметров для функций представляет собой переменные, разделенные запятыми, как и в Python. И снова C требует указания типов всех параметров, так что SemLockObject *self сообщает, что первый параметр содержит указатель на SemLockObject и называется self. Все параметры в C являются позиционными.
А теперь разберемся, что же означает «указатель» в этой команде.
Добавим немного контекста: параметры передаются функциям C по значению. Это означает, что функция работает с копией значения, а не с исходным значением в вызывающей функции. Чтобы обойти это ограничение, функциям часто передается адрес данных, чтобы эти данные могли изменяться функцией.
Эти адреса называются указателями и обладают типом. Так, int * — указатель на целое число, и этот тип отличается от double * — указателя на число с плавающей точкой с двойной точностью.
Указатели
Как упоминалось ранее, указатели представляют собой переменные, которые хранят адрес значения. Они часто используются в C, как показано в следующем примере:
static PyObject *
semlock_release(SemLockObject *self, PyObject *args)
{
<команды тела функции>
}
Здесь параметр self содержит адрес значения SemLockObject (то есть указатель на него). Также обратите внимание на то, что функция возвращает указатель на значение PyObject.
В C существует специальное значение NULL; оно сообщает, что указатель ни на что не указывает. В исходном коде CPython присваивание NULL и проверка указателей на NULL встречаются достаточно часто. Такие проверки важны,
Книги для программистов: https://t.me/booksforits
Базовый синтаксис C 347
потому что ограничения на возможные значения указателей минимальны, а обращение к адресу памяти, не принадлежащему вашей программе, может привести к ее странному поведению.
С другой стороны, если вы попытаетесь обратиться к памяти по адресу NULL, программа немедленно завершится. Может показаться, что это слишком радикальное решение, но обычно проще искать ошибку памяти, проверяя указатель на NULL, чем проверяя, что некоторый адрес в памяти был изменен.
Строки
В языке C нет строкового типа. Существует условное соглашение, на базе которого написаны многие функции стандартной библиотеки, но реального типа не существует. Строки в C хранятся в виде массивов char (для ASCII) или wchar (для Юникода); каждый из этих типов содержит один символ. Строки помечаются нуль-терминатором, то есть значением 0, которое обычно обозначается в коде как \0.
Основные строковые операции, такие как strlen(), полагаются на расположение нуль-терминатора, отмечающего конец строки.
Так как строки представляют собой массивы значений, их нельзя сравнивать или копировать напрямую. Для выполнения этих операций в стандартной библиотеке имеются функции strcpy() и strcmp() (и их аналоги для wchar).
Структуры
В последнем разделе нашего мини-обзора C рассмотрим возможность создания новых типов в C. Ключевое слово struct позволяет сгруппировать набор разных типов данных в новый нестандартный тип данных — структуру:
struct <имя_структуры> { <тип> <имя_поля>; <тип> <имя_поля>;
...
};
Следующий фрагмент из файла Modules/arraymodule.c демонстрирует объявление структуры:
struct arraydescr { char typecode;
Книги для программистов: https://t.me/booksforits