LINUX.ORG.RU

Контейнеры в C


2

3

Будучи сиплюсплюсником, меня давно интересует как те же задачи выполняются в C. Знаю, на ЛОРе есть множество приверженцев C, надеюсь они ответят на пару простых вопросов.

Я являюсь активным сторонником идеи «Алгоритм должен работать настолько быстро, насколько позволяет железо». С этой точки зрения C++ даёт потрясающие возможности из-за своей системы кодогенерации. Да, я имею в виду шаблоны.

Не будем зарываться в дебри boost'а, возьмём простую задачу - контейнеры.

Для примера я взял односвязные списки в GTK GSList и Qt QList. QList позволяет хранить как простые, так и сложные типы с конструкторами, деструкторами, типы с общими данными. При этом накладных расходов на выделение в куче не происходит, а при реаллокации выбирается нужный алгоритм в зависимости от QTypeInfo<T>, к примеру для int будет вызван memcpy(), а для QString оператор копирования. Кроме того блок данных заранее резервируется и для сложных типов вызывается placement constructor. Для удаления данных по указателям используется алгоритм qDeleteAll(from,to), который сам дёрнет нужные конструкторы. Беглый просмотр GSList показал, что в нём можно хранить только указатели, со всеми вытекающими накладными расходами на аллокацию/уничтожение памяти, ручное кастирование, слежение за утечками, фрагментацию памяти.

Аналогичная ситуация со связными списками GList и QLinkedList.

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

Далее, счётчики ссылок и деструкторы.

К примеру, в C++ список строк будет выглядеть как QList<QString>, при этом можно забыть про внутреннюю структуру строки - конструктор, деструктор и операторы копирования сами занимаются подсчётом ссылок, разделением и уничтожением данных. Вернуть строку из функции проще простого:

QString foo()
{
    return listOfStrings.at( 5 );
}

Этот код абсолютно безопасен и оптимален с точки зрения использования памяти и процессора, поскольку время тратится только на увеличение счётчика и копирование указателя.

В GTK я сходу нашёл GString:

struct GString 
{
  gchar *str;
  gint len;
};

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

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

Или может GTK неудачный пример, тогда дайте ссылку на правильную C-библиотеку с контейнерами.

★★★★★
Ответ на: комментарий от ananas

можно пример необходимости такого перераспределения?


struct A {
};

struct B {
    A[2] a;
    A* pa;
};

...

struct B *b = (B*)malloc( sizeof(struct B) );
b.pa = &B.a[1];

А теперь скопируй

namezys ★★★★
()
Ответ на: комментарий от MuZHiK-2

> Зачем она здесь? Или ты думаешь, что через какой-то эфир в моем списке поменяется тип хранимых данных?

Да, через лет 5, когда код расползется на 50 файлов может и поменятся

namezys ★★★★
()
Ответ на: комментарий от namezys

Только не надо рассказывать здесь, что ты все помнишь. И все, кто работают в твоей команде, помнят все, что пишет сосед.

namezys ★★★★
()
Ответ на: комментарий от namezys

>>Да, через лет 5, когда код расползется на 50 файлов может и поменятся

Если ты пишешь код, и при это не понимаешь толком, что пишешь, то тогда лучше не писать. А когда код на 50 файлов, в нормальных проектах применяют юнит-тесты.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от MuZHiK-2

> когда тебе говорят «подожди пару секунд»

то я не против подождать - но ты такое не говорил

П.С. насчет твоей полной немощности был не прав - признаю, за 20 минут решить задачу в несколько строк ты можешь

ahonimous
()
Ответ на: комментарий от namezys

а можно пример не из университетских лабораторных заданий, а из более приближенных к реальной жизни?

ananas ★★★★★
()
Ответ на: комментарий от namezys

>>Только не надо рассказывать здесь, что ты все помнишь. И все, кто работают в твоей команде, помнят все, что пишет сосед.

Нет, блин, они пишут код чисто наобум - скомпилилось и ладно.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от MuZHiK-2

Мне это начинает напоминать динамические языки. Только динамических языках тебе вылетит exception с указанием причины, а здесь неизвестно еще какая ошибка - и ищи ее.

namezys ★★★★
()
Ответ на: комментарий от ahonimous

>>то я не против подождать - но ты такое не говорил

давай без шлангов.

П.С. насчет твоей полной немощности был не прав - признаю, за 20 минут решить задачу в несколько строк ты можешь

Потому что на работе есть другие дела помимо ЛОРа.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от namezys

> Только не надо рассказывать здесь, что ты все помнишь. И все, кто работают в твоей команде, помнят все, что пишет сосед.

код не комментируем?

ananas ★★★★★
()
Ответ на: комментарий от namezys

>>Мне это начинает напоминать динамические языки. Только динамических языках тебе вылетит exception с указанием причины, а здесь неизвестно еще какая ошибка - и ищи ее.

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

MuZHiK-2 ★★★★
()
Ответ на: комментарий от ahonimous

>>и ты даже ссылку на сообщение даешь?

А ты первый раз слышишь употребление слова «пару <чего-то>» в смысле, отличном от «два <чего-то>»? Тогда почаще общайся с людьми и выходи на улицу.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от namezys

>>Блин, я щас буду сидеть, придумывать. И постить код на 10000 строк.

А придумывать другую хрень и постить ты можешь.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от MuZHiK-2

> При нормальной организации процесса таких проблем не возникает.

В С++ их вообще не возникает. А при тестировании явных типонезависмых преобразвоания надо по хорошему тестировать все возможные такие преобразования

namezys ★★★★
()
Ответ на: комментарий от MuZHiK-2

> А придумывать другую хрень и постить ты можешь.

Хрень придумываете вы, чтоб не признавать силу С++ в той области, где он действительно силен.

namezys ★★★★
()
Ответ на: комментарий от namezys

вот то-то и оно. весь этот гимор с копированием нужен только при кривом дизайне программы. а в твоем примере все спокойно разруливается обычным define-ом

ananas ★★★★★
()
Ответ на: комментарий от MuZHiK-2

> А ты первый раз слышишь употребление слова «пару <чего-то>»

ничего про подождать «пару <чего-то>» ты мне не писал - это ты на солнышке перегрелся, или помещение у вас не проветривается, или еще что-то нехорошее

ahonimous
()
Ответ на: комментарий от namezys

>>В С++ их вообще не возникает.

Еще один. В С++ уже нету такой штуки, как void* ?

А при тестировании явных типонезависмых преобразвоания надо по хорошему тестировать все возможные такие преобразования

Надо просто думать, когда код пишешь.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от namezys

c++ - это тот самый молоток, с которым любая проблема кажется гвоздем

ananas ★★★★★
()
Ответ на: комментарий от namezys

> Только без удаления ссылки внутрь объекта

только после того, как ты обоснуешь ее необходимость

ananas ★★★★★
()
Ответ на: комментарий от MuZHiK-2

> Еще один. В С++ уже нету такой штуки, как void* ?

ССЗБ если используешь.

Надо просто думать, когда код пишешь.

Можно и на ассемблере писать тогда уж. Чтож компилятор нагружать

namezys ★★★★
()
Ответ на: комментарий от ahonimous

>>ничего про подождать «пару <чего-то>» ты мне не писал - это ты на солнышке перегрелся, или помещение у вас не проветривается, или еще что-то нехорошее

А должен был написать? Я тебе вообще ничего не должен был писать, но для публичной порки уделил пару минут для написания пары строчек кода.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от namezys

>>ССЗБ если используешь.

Ну вот так же и я тебе отвечаю на твои возражения с преобразованиями: ССЗБ если криво пишешь код.

Можно и на ассемблере писать тогда уж. Чтож компилятор нагружать

Давай не отмазывайся. Еще про дельфи вспомни.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от Dendy

>В итоге, с моей точки зрения, имеем максимальную скорость работы алгоритма, минимальное использование памяти и безопастный код.

В итоге в твоем коде имеют место быть типичные для плюсоводов проблемы: «это работает и я не хочу напрягать свой мозжечок, чтобы осознать, что оно работает отвратительно».

Для чего тебе нужно хранить копии объекта Person в хэше и списке? Как ты реализуешь быстрое удаление из списка? Как ты реализуешь синхронизацию хэша и списка?

linuxfan
()
Ответ на: комментарий от namezys

>>Обычно ошибки приведения типов происходят не из-за слабой памяти

А из-за кривых рук, мы в курсе.

MuZHiK-2 ★★★★
()

EPIC WIN

И вроде топик поставлен убержирно. И вроде спор поражает своей бессмысленностью. А уже 6 страниц.

Моя не понимать.

pathfinder ★★★★
()
Ответ на: комментарий от MuZHiK-2

> А должен был написать?

но для публичной порки уделил пару минут для написания __пары строчек__ кода.


пациент, да у вас тяжелая форма амнезии

ahonimous
()
Ответ на: комментарий от Cy6erBr4in

>предвосхищая ответ навроде «ну чтобы память не расходывать, чтобы данные не копировались», отвечу, что Dendy как раз и показывал, как Си++ офигенно разруливает работу с памятью автоматически, без вмешательства программиста

А потом мы сидим и думаем: чего это фаерфокс так медленно рендерит? А он, оказывается, занимается типичным глубоким копированием сложных структур данных совершенно прозрачно от пользователя и программиста.

linuxfan
()
Ответ на: EPIC WIN от pathfinder

Моя тоже. И не понять

Я понимаю ущербность С++, но когда С подпирают подпорками - он становит еще ущербней

namezys ★★★★
()
Ответ на: комментарий от ahonimous

>>пациент, да у вас тяжелая форма амнезии

Еще раз для тугих: не всегда понятие «пара» отождествляется с понятием «два», зачастую оно отождествляется с понятием «несколько», если для тебя это будет понятнее.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от linuxfan

> А потом мы сидим и думаем: чего это фаерфокс так медленно рендерит?

у вас есть более быстрые аналоги не на С++? если что Chrome/Safari/etc. - это WebKit на С++ и обвязка вокруг

ahonimous
()
Ответ на: комментарий от MuZHiK-2

> Еще раз для тугих: не всегда понятие «пара» отождествляется с понятием «два», зачастую оно отождествляется с понятием «несколько», если для тебя это будет понятнее.

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

ahonimous
()
Ответ на: комментарий от Dendy

>Внутри такого списка каждое его звено будет выделяться в памяти (для быстрой операции вставки и удаления из середины), но сами элементы внутри хранятся по значениям

Открой уже для себя #include <sys/queue.h>

linuxfan
()
Ответ на: комментарий от ahonimous

>> Еще раз для тугих: не всегда понятие «пара» отождествляется с понятием «два», зачастую оно отождествляется с понятием «несколько», если для тебя это будет понятнее.

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

http://gramota.ru/slovari/dic/?bts=x&word=%EF%E0%F0%E0

седьмой пункт

unC0Rr ★★★★★
()
Ответ на: комментарий от MuZHiK-2

Это не пара строк и даже не несколько, а десяток. Освобождение ресурсов стыдливо опустил ? Этот пример не эквивалентен C++ коду, ибо тут нет отсортированности и ключом может быть только строка. Кстати еще не понятно как внутри оно работает, ибо документация glib'а это умалчивает, поэтому не ясно за какое время будет выполняться поиск и вставка.

Reset ★★★★★
()

Чем не статический полиморфизм в Си:


/* Перечислили нужные параметры сложных типов */

typedef enum {
    ATOM,
    APPLICATION,
    FUNCTION,
    CONS
} Type;

/*
 * Ввели эти сложные типы, причём тут все структуры
 * бинарно совместимы по первому полю, что и позволяет делать СП
 */
typedef struct {
    Type type;
} Term;

typedef struct {
    Type   type;
    char *name;
} Atom;

typedef struct {
    Type type;
    Term *head;
    Term *tail;
} Cons;

/* Сделали аккессоры */

#define type(X)      (((Term *) (X))->type)
#define name(X)      (((Atom *) (X))->name)
#define head(X)      (((Cons *) (X))->head)
#define tail(X)      (((Cons *) (X))->tail)
#define first(X)     (head(X))
#define second(X)    (head(tail(X)))
#define third(X)     (head(tail(tail(X))))
#define fourth(X)    (head(tail(tail(tail(X)))))

/* Аллокаторы */

#define ALLOCATE_IT_OBJECT(TYPE)                                               \
    TYPE *IT;                                                                  \
    number_of_nodes++;                                                         \
    IT = (TYPE *) malloc (sizeof (TYPE))

#define RETURN_IT_OBJECT                                                       \
    return (Term *) IT

/* конструкторы */

static inline Term *
make_atom (char *name) {
    ALLOCATE_IT_OBJECT(Atom);
    IT->type = ATOM;
    IT->name = name;
    RETURN_IT_OBJECT;
}

static inline Term *
make_cons (Type type, Term *head, Term *tail) {
    ALLOCATE_IT_OBJECT(Cons);
    IT->type = type;
    IT->head = head;
    IT->tail = tail;
    RETURN_IT_OBJECT;
}

/* И такой полиморфный "метод" */

void
print (Term *term) {

    switch (type(term)) {

        case ATOM:
            printf(name(term));
            break;

        case APPLICATION:
            printf("(");
            while (1) {
                print (head(term));
                term = tail(term);
                if (term == nil) {
                    printf (")");
                    break;
                }
                else if (type(term) != APPLICATION) {
                    printf (" . ");
                    print (term);
                    printf (")");
                    break;
                }
                printf (" ");
            };
            break;

        case FUNCTION:
            fputc ('<', output_stream);
            do {
                print (head(term));
                term = tail(term);
                if (term == nil) {
                    fputc ('>', output_stream);
                    break;
                }
                else if (type(term) != FUNCTION) {
                    fputs (" . ", output_stream);
                    print (term);
                    fputc ('>', output_stream);
                    break;
                }
                fputc (' ', output_stream);
            } while (1);
            break;

        default:
            error ("Cannot print object: invalid type.");
    }
}

(хм, точнее runtime статический полиморфизм)

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Да. И в коде нет проверок на валидность входных данных функции. Легко сделать ошибку, и не синхронизировать реальный тип и ему в нем.

namezys ★★★★
()
Ответ на: комментарий от MuZHiK-2

Чисто так, для показательной порки:

   GData *data = NULL; 
   gpointer ptr; 
 
   typedef struct { 
      gchar* nick; 
      gint id; 
   } person; 
 
   person Prs = {g_strdup ("Dendy"), 38}; 
 
   g_datalist_set_data (&data, Prs.nick, &Prs); 
 
   ptr = g_datalist_get_data (&data, Prs.nick); 
 
   g_print ("%s: %d\n", ((person *) ptr)->nick, ((person *) ptr)->id); 

Ну вот уже кое-что, за три страницы срача. Спасибо кстати, я GLib не юзал - познавательно. Пример достаточно показателен и при невозможности заюзать С++, такое решение вполне себе неплохо.

Но в контексте спора, а ТС хотел увидеть собственно еще и как организовать копирование объекта и удаление из списка и все это с «автоматическим» распределением/удаление памяти. Понятно что можно и это дописать, но будет уже не «пару строк», а то и больше. А уж если сделать проверку типа объекта на который ссылается указатель, наберется страниц еще несколько (в С++ это можно переложить на компилятор), я кстати слабо представляю себе как это можно сделать на С. к тому же это врядли спасет от передачи указателя на структуру типа:

   typedef struct {
      person base; 
      int grade; 
   } super_person; 

shelA
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.