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-библиотеку с контейнерами.

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

> в win таки запилили wchar32_t

хорошая новость( даже наверное «старость» - я просто не слежу )

да не, просто у меня неиллюзорный баттхёрт с этим был, а тут под руку vs2010 подвернулся ну я и промониторил заодно :)

ЗЫ всё равно я на них забил и использую ICU :)

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

>Даже такая элементарная логика должна подсказать, что это невозможно.

Я знаю что массив под элементы выделяется на хипе. Но выделяется он один раз, затем происходят реаллоки. В примере на glib дополнительно выделяется память под структуры person, и в GList сохраняются лишь указатели. А в Qt person выделяется на стеке, и данные копируются. Так что примеры не равнозначны.

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

>и это вместо

Так просто, ради интереса, многие ли компиляторы уже поддерживают эту фичу грядущего цпп-стандарта?

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

Ну и что. Зато работает qt быстрее даже в этом случае

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

> Так просто, ради интереса, многие ли компиляторы уже поддерживают эту фичу грядущего цпп-стандарта?

clang не держит

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

use case пожалуйста

чего? я копирую одну строку - получаю две. то есть побайтовое копирование. copy-on-write дешевле

ЕМНИП в std::string есть CoW

> это Вы про что?

Можно каждый раз не выделать память из кучи при работе со строками - использовать более быструю аллокацию

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

> интерфейс какой библиотеки Вас радует?

boost'а больше. более С++ way. Критики stl среди С++ много

эм, не сказал бы что он больше C++-way, но то лирика

вообще давно пора переходить на c++0x, там много хороших вещей есть

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

> Так просто, ради интереса, многие ли компиляторы уже поддерживают эту фичу грядущего цпп-стандарта?

gcc поддерживает

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

>чёт я там не увидел ничего уникального и нужного одновременного, может просмотрел?

Строки хранятся в одном блоке памяти вместе со структурой. Попробуй изобразить такое с std::string/QString.

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

начиная с 4.5 ( которая у меня в убунту 10.10 в репозитории есть )

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

>и пусть там будут длинные строки - нам не лень два раза вызвать strlen при каждой вставке

Где вызывается strlen второй раз?

вообще красота - надо изменить значение в структуре на более длинное

Опять теоретики разбушевались?

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

>gcc поддерживает

Начиная с какой версии, лол? С 4.5? Закопайте эту сырость!

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

ЕМНИП в std::string есть CoW

для gcc - да, у msvc такого нет

эм, да теперь и правда отпилили, раньше было

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

> Где вызывается strlen второй раз?

«unsigned int nick_l = strlen(nick), name_l = strlen(name)»

Опять теоретики разбушевались?


ну да - структуры они ведь не для этого, в них просто хранят статичные данные

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

>unsigned int nick_l = strlen(nick), name_l = strlen(name)

А, ты живешь в какой-то параллельной вселенной, где для копирования строки нет необходимости пройтись по ней целиком? Или ты не различаешь strlen(nick) и strlen(name)?

ну да - структуры они ведь не для этого, в них просто хранят статичные данные

В реальной жизни обычно так оно и есть.

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

> Щазз, все бросил, побежал школьные лабораторные писать.

ну да. у меня ушло минут 7, чтоб набросать. у вас часа 2 уйдет - столько времени жалко. Особенно, когда оно еще будет работать в разы медленее

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

> А, ты живешь в какой-то параллельной вселенной, где для копирования строки нет необходимости пройтись по ней целиком?

в моей вселенной есть std:string, где не надо проходить по всемй строке, чтоб найти ее длину

В реальной жизни обычно так оно и есть.


у вас очень статичная реальная жизнь

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

>> и что в них нехорошего (за вычетом отсутствия юникода)?

копирование буфера при копировании строки.

Не нужно, есть константные ссылки. В новом стандарте cow запретят и правильно сделают.

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

>чёт я там не увидел ничего уникального и нужного одновременного, может просмотрел?

Строки хранятся в одном блоке памяти вместе со структурой. Попробуй изобразить такое с std::string/QString.

я правильно понимаю, речь идёт о: http://www.linux.org.ru/jump-message.jsp?msgid=5154097&cid=5155824?

если да, то вопрос: зачем это нужно?

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

Кресты - говно

Проверял предложенный кем-то алгоритм поиска в интернетах форума по заданной теме: отправлял в гугель запрос «<субж> - говно». По разным <субж> в первых строках ЛОР. Подзасрали интернеты, да.

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

Не совсем. Та используется простой аллокатор. Выделяют, например 32*1024 блоком, потом выдают по 32 байта с помощью битовой маски. быстрый поиск подходящего кусочка, быстрое возращение

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

>у вас очень статичная реальная жизнь

Ты будешь удивлен, но после университета все действительно статично.

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

> Ты будешь удивлен, но после университета все действительно статично.

Я вам сочуствую

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

>если да, то вопрос: зачем это нужно?

В принципе после этого вопроса можно сворачивать дискуссию за недостатком опыта у одной из сторон. Подумай на досуге (hint: сколько операций выделения памяти в таком примере?)

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

Не совсем. Та используется простой аллокатор. Выделяют, например 32*1024 блоком, потом выдают по 32 байта с помощью битовой маски. быстрый поиск подходящего кусочка, быстрое возращение

эм, Вы на вот это сообщение отвечали? а то я не очень пойму логику

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

>вот вот. аллокация строк - операция тяжелая, если ее на куче делать

Как интересно... А в куске кода ниже сколько выделений памяти на куче будет (STL от SGI)?

void foo() {
  std::string bar("bar");
}
linuxfan
()
Ответ на: комментарий от linuxfan

> Строки хранятся в одном блоке памяти вместе со структурой. Попробуй изобразить такое с std::string/QString.

пишется свой аллокатор, который отдается в конструктор( сам не пробовал - но возможность такая есть ), но зачем? если данные 100% статичны, то и в С++ можно их скопом как массив байтов хранить

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

>> Не нужно, есть константные ссылки. В новом стандарте cow запретят и правильно сделают.

почему?

Лень гуглить комментарии комитета, но лично мне cow ну ни разу не пригождалась. Т.е. ссылки считаются, такты тратятся, толку 0 (ноль).

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

>пишется свой аллокатор, который отдается в конструктор

Похоже, об аллокаторах ты даже в книжках не читал. Просветись на эту тему: в STL аллокатор везде — это параметр шаблона со всеми вытекающими последствиями.

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

если да, то вопрос: зачем это нужно?

В принципе после этого вопроса можно сворачивать дискуссию за недостатком опыта у одной из сторон. Подумай на досуге (hint: сколько операций выделения памяти в таком примере?)

хы, вот уж точно за недостатком опыта :)

1. man pooling allocator

2. use mystr.resize(n);

а то что Вы пытаетесь показать - это забивание гвоздей поперёк, хотя все знают что надо вдоль :)

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

вот вот. аллокация строк - операция тяжелая, если ее на куче делать

шо там тяжёлого? чем оно отличается от аллокации любых других типов данных?

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

>1. man pooling allocator

Ты уверен, что правильно понимаешь, для чего нужны выделения памяти в пуле?

2. use mystr.resize(n);

Это тут причем?

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

>>пишется свой аллокатор, который отдается в конструктор

Похоже, об аллокаторах ты даже в книжках не читал


не буду спорить - не читал

Просветись на эту тему: в STL аллокатор везде — это параметр шаблона со всеми вытекающими последствиями.


что не мешает иметь аллокатор в списке параметров, его наличие меня и смутило, хотя да - надо обязательно указывать как параметр шаблона

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

>1. man pooling allocator

Ты уверен, что правильно понимаешь, для чего нужны выделения памяти в пуле?

если есть что сказать - говорите, нет - проходитe

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

>что не мешает иметь аллокатор в списке параметров

В каком же месте: http://www.sgi.com/tech/stl/basic_string.html ?

Кстати, такой забавный момент, что basic_string<..,my_allocator> и basic_string<..,another_allocator> являются разными типами и, соответственно, между ними невозможны никакие операции тебя ничуть не смущает?

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

>если есть что сказать - говорите, нет - проходитe

Как ты представляешь себе использование выделения памяти в пуле для того, чтобы положить в один блок class Person его строковые поля nick и name?

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

Кстати, такой забавный момент, что basic_string<..,my_allocator> и basic_string<..,another_allocator> являются разными типами и, соответственно, между ними невозможны никакие операции тебя ничуть не смущает?

ну разные типы, ну нельзя без допиливания написать

str_type1 = str_type2;
но ничего тут страшного

а вот то что между ними невозможны никакие операции - это Вы крепко гоните

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

если есть что сказать - говорите, нет - проходитe

Как ты представляешь себе использование выделения памяти в пуле для того, чтобы положить в один блок class Person его строковые поля nick и name?

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

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

В каком же месте: http://www.sgi.com/tech/stl/basic_string.html ?

я смотрел тут:

http://www.cplusplus.com/reference/string/string/string/

Except for the copy constructor, an optional final parameter exists for all basic_string constructors, whose type is its Allocator template argument. This parameter influences the memory allocation model to be used for the object. To provide a better readability, and because under no known compiler implementation the memory allocation model for strings (allocator<char>) is affected by its value, it has not been included in the declarations above, but see the basic template member declaration ahead for a more complete declaration.

но опять же не буду спорить - делать через basic_stream костыльно и не переносимо( на примере того же sgi's stl )

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

нет - потому-что, как я и говорил, я ни разу так не делал :) предлагаю закрыть вопрос аллокаторов - я согласен, что это не вариант

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

>ну нельзя без допиливания написать

Ты себе примерно представляешь, какого уровня допиливание нужно для такого? Это тебе не operator==. Оператор присваивания обязательно должен быть членом класса.

Дело в том, что ты рассуждаешь, основываясь непонятно на чем (но очевидно, что не на практическом опыте), а я поимел пару неприятных недель дружбы бустовых пул-аллокаторов со сложными stl-based структурами данных. Насколько мне это не понравилось, даже трудно выразить словами.

а вот то что между ними невозможны никакие операции - это Вы крепко гоните

Конкатенация? Сравнение? Копирование? А больше ничего и нет. Или ты намекаешь на черезжопное c_str()?

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

>предлагаю закрыть вопрос аллокаторов - я согласен, что это не вариант

Ok, а как тогда реализовать выделение памяти под структуру и ее данные в одном блоке? Я согласен с тем, что на такие трюки пофиг, если ты делаешь адресную книгу деревни Старые Васюки на 4 гигабайтах оперативки. Но бывают в жизни такие ситуации, когда в памяти нужно уместить пару сотен миллионов небольших записей, где оверхед в 8 байт на блок памяти в куче (не говоря уже о 16-байтном выравнивании) становится ой как ощутим. В таких случаях как раз и приходится паковать, выигрывая абсолютно по всем показателям: скорость, фрагментация, объем занимаемой памяти.

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