Рубрики
Без рубрики

Принудительное наследование в C с использованием структурного состава

C Язык не поддерживает наследование, однако он поддерживает композиции структуры, которые могут быть T … Tagged с Python, CPP, OOP, Linux.

C Language не поддерживает наследование, однако он поддерживает композиции структуры, которые можно настроить для обслуживания вакансий, требующих отношений между родителями и детьми. В этой статье мы узнаем, как структурные композиции помогают нам подражать наследованию в C и сохранять расширение нашего кода. Мы также обнаружим, как это приводит к тому, что он приводит к тому, что когда -либо изобретено в области компьютерных наук.

Структурная композиция – это когда мы помещаем одну структуру в другую, не через его указатель, а как родной член – что -то вроде этого

// this structure defines a node of a linked list and
// it only holds the pointers to the next and the previous
// nodes in the linked list.
struct list_head {
    struct list_head *next; // pointer to the node next to the current one
    struct list_head *prev; // pointer to the node previous to the current one
};

// list_int holds an list_head and an integer data member
struct list_int {
    struct list_head list;  // common next and prev pointers
    int value;              // specific member as per implementation
};

// list_int holds an list_head and an char * data member
struct list_str {
    struct list_head list;  // common next and prev pointers
    char * str;             // specific member as per implementation
};

В приведенном выше примере мы определяем узел связанного списка с использованием структурного состава. Обычно в связанном узле списка есть 3 участника – два указателя на соседние узлы (следующий и предыдущий), а третий может быть либо данные, либо указатель на него. Определяющим фактором связанного списка являются два указателя, которые логически образуют цепочку узлов. Чтобы все было абстрактным, мы создаем структуру с именем List_head который держит эти два указателя Следующий и предыдущий и опускает специфики, то есть данные.

Используя List_head Структура, если бы мы определили узел связанного списка, содержащего целочисленное значение, мы могли бы создать другую структуру, названную list_int это держит член типа List_head и целочисленное значение ценность . Следующие и предыдущие указатели вносятся в эту структуру через LIST_HEAD LIST и можно назвать list.next и List.prev Анкет

Существует очень подлинная причина для выбора таких странных имен для связанного списка узла и членов структур; Причина для этого будет очищена в более поздних разделах этого эссе.

Из -за вышеуказанного определения структуры построение связанного списка узел узел любого типа становится бризом. Например, строка, удерживающая узел, может быть быстро определен как структура List_str имея List_head и char * Анкет Эта способность расширять List_head и создайте узел, содержащий данные любого типа, и любые специфики делают низкоуровневый код простым, равномерным и расширяемым.

Представление памяти List_int

Структуры в C не прокладываются, и они даже не содержат мета -информацию, даже для имен участников; Следовательно, во время распределения они выделяют пространство достаточно, чтобы хранить фактические данные.

На приведенной выше иллюстрации мы видим, как члены list_int нанесены на карту на выделенном пространстве – требуемые его отдельными членами. Он выделяется смежное пространство 12 байтов – 4 байта для каждого из двух указателей и еще 4 байта для целочисленного значения. Смелость распределения пространства и порядок членов во время распределения может быть подтверждена путем распечатки их адресов, как показано ниже.

void print_addrs() {
    // creating a node of the list_int holding value 41434
    struct list_int *ll = new_list_int(41434);

    // printing the address of individual members
    printf("%p: head\n",             head);
    printf("%p: head->list.next\n",  &((head->list).next));
    printf("%p: head->list.prev\n",  &((head->list).prev));
    printf("%p: head->value\n",      &(head->value));
}

~ $ make && ./a.out
0x4058f0: head
0x4058f0: head->list.next
0x4058f4: head->list.prev
0x4058f8: head->value

Мы четко видим всех 3 членов, занимающих 12 байт смежных сегментов памяти в порядке их определения в структуре.

Приведенный выше код был выполнен на машине, где размер целого числа и указателей составлял 4 байта каждый. Результаты могут отличаться в зависимости от архитектуры машины и процессора.

Указатели кастинга, указывающие на структуру

На языке C, когда указатель на структуру отбрасывается на указатель на другую структуру, двигатель отображает отдельные элементы типа целевой структуры, в зависимости от их порядка и смещений, к срезам памяти экземпляра исходной структуры.

Когда мы бросаем list_int * в List_head * , двигатель отображает пространство, требуемое целевым типом, то есть List_head на космосе, занятом list_int Анкет Это означает, что он отображает 8 байтов, требуемых для List_head На первых 8 байтах, занятых list_int экземпляр. Пройдя по представлению памяти, обсуждаемой выше, мы обнаруживаем, что первые 8 байтов list_int на самом деле List_head и, следовательно, кастинг list_int * к List_head * фактически просто ссылается на List_head Член list_int через новую переменную.

Это эффективно строит взаимосвязь между родителями и детьми между двумя структурами, где мы можем безопасно типировать ребенка list_int своему родителю List_head .

Здесь важно отметить, что отношения между родителями и ребенком устанавливаются только потому, что первый член list_int типа List_head . Это не сработало бы, если бы мы изменили порядок членов в list_int Анкет

Как установлено выше, вкладывая одну структуру в другую в качестве первого элемента, мы эффективно создаем взаимосвязь между родителями и детьми между ними. Поскольку это дает нам возможность безопасно типировать ребенка для своего родителя, мы можем определить функции, которые принимают указатель на родительский структуру как аргумент, и выполнять операции, которые на самом деле не требуются для работы с спецификами. Это позволяет нам Не Перепишите функциональную логику для каждого дочернего расширения и, таким образом, избегайте избыточного кода.

Из контекста, который мы настроили, скажем, мы хотим написать функцию, которая добавляет узел между двумя в связанном списке. Основная логика для выполнения этой операции на самом деле не нужно иметь дело с какими -либо особенностями, которые требуется, – это несколько манипуляций с указателями Следующий и предыдущий . Следовательно, мы могли бы просто определить функцию, принимающую аргументы типа List_head * и написать функцию как

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static void __list_add(struct list_head *new,
                       struct list_head *prev,
                       struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}

Поскольку мы можем безопасно typecase list_int * и List_str * к List_head * Мы можем передать любую из конкретных реализаций функции __list_add И это все равно будет добавлять узел между двумя другими беспрепятственными.

Поскольку основные операции в связанных списках требуют только манипуляций с указателями, мы можем определить эти операции как функции, принимающие List_head * вместо конкретных типов, таких как list_int * Анкет Таким образом, нам не нужно писать аналогичные функции для специфики. Функция удаления узла может быть написана как

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;
}

Другие утилиты связанных списков, такие как Добавление узла в хвост , Обмен узлы , Сплайсинг список , Вращение списка и т. Д. Требуется только манипуляции Следующий и PREV указатели. Следовательно, они также могут быть написаны очень похожими способами, т. Е. Принимая List_head * и, таким образом, устранение необходимости повторного внедрения логики функции для каждой реализации ребенка.

Такое поведение очень похоже на то, как наследство на современных языках ООП, таких как Python и Java, работа, где ребенку разрешено вызывать любую родительскую функцию.

Есть тонна практического использования структурных композиций, но самые известные находятся

Linux ядра

Чтобы сохранить все абстрактные и расширяемые, Linux Kernel использует структурный состав в нескольких местах. Одно из самых важных мест, где он использует композицию, предназначена для управления и поддержания связанных списков, именно то, как мы видели вещи выше. Определения структуры и фрагменты кода взяты как есть из Исходный код ядра и, следовательно, структура и имена переменных выглядят не так, как обычно.

Тип питона и иерархия объекта

Python, один из самых важных языков в современном мире, использует структурную композицию для создания иерархии типа. Python определяет корневую структуру, называемую Pyobject который содержит количество ссылок, определяя количество мест, из которых ссылается объект – и тип объекта – определение типа объекта, т.е. int , стр , список , дикта , и т.д.

typedef struct _object {
    Py_ssize_t     ob_refcnt;  // holds reference count of the object
    PyTypeObject   *ob_type;   // holds the type of the object
} PyObject;

Поскольку Python хочет, чтобы эти поля присутствовали в каждом отдельном объекте, создаваемом во время выполнения, он использует структурную композицию, чтобы гарантировать, что такие объекты, как целые числа, поплавки, строка и т. Д. Pyobject как их первый элемент и, таким образом, устанавливает отношения между родителями и ребенком. Поплавок в Python определяется как

#define PyObject_HEAD PyObject ob_base;

typedef struct {
    PyObject_HEAD
    double ob_fval;    // holds the actual float value
} PyFloatObject;

Теперь написание функций утилиты, которые увеличивают и уменьшают ссылки на каждый доступ к любому объекту, можно записать как единственная функция, принимающая Pyobject * как показано ниже

static inline void _Py_INCREF(PyObject *op) {
    op->ob_refcnt++;
}

Таким образом, мы уничтожаем потребность в переписывании Incref Для каждого типа объекта и просто напишите его один раз для Pyobject и он будет работать для каждого типа объекта Python, который распространяется через Pyobject Анкет

Если вам понравилось то, что вы читали, рассмотрите возможность подписаться на мою еженедельную рассылку по адресу arpitbhayani.me/newsletter Были раз в неделю, я пишу эссе о внутренних группах языков программирования или глубоком погружении по некоторому алгоритму супер-обработке или лишь несколько советов по созданию очень масштабируемых распределенных систем.

Вы всегда можете найти меня просматривать Twitter @arpit_bhayani Анкет

Оригинал: “https://dev.to/arpit_bhayani/powering-inheritance-in-c-using-structure-composition-20jk”