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

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

> И я просто не пойму, чего этот чел тут пропагандировал?)

очевидно создание адресных книг на 32Гб, которые нельзя хранить на жестком( только в ОЗУ ) и в которых нельзя менять записи

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

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

либо менять глобальный new с возможностью выставить адрес для следующего вызова new, но это, мягко говоря, костыльно

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

> либо менять глобальный new с возможностью выставить адрес для следующего вызова new, но это, мягко говоря, костыльно

Это не мягко говоря плохо

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

расскажу потом об идея специфичных аллкаторов и сохранения типа вызываемого new прямо в объекте

namezys ★★★★
()

Кстати интересно, почему тема не вылезла на главную в топ 10 наиболее обсуждаемых?))) Висит там какой-то бред по 13 страниц обсуждения. А у нас то уже 16 )))

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

> почему - вылезла уже

И действительно на 7 позиции ))) Видимо я когда смотрел или мои глазные тормоза сработали ))) или Маскомовский тормозной скрипт ещё не стработал)))

Пора подводить итоги )))

Не хочется категорично заявлять, но те кто ратовал за «непокобелимость» пюре Си по сравнению С++ в ООП делах - потерпели фиаско, подкрепленное исходными кодами тестов и результатами, а так же дискасом по теме ))) Отдельное спасибо ЛИПСКлоунам, которые скрашивали паузы между выступлениями плюсовиков и сишников )))

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

добавлю от себя :)

1. glib - многословно и медленно
2. кроме glib были предложены еще варианты для С - вплоть до велосипедов, что является отрицательным моментом, в С++, например, есть stl - как единый стандарт, и даже сторонние велосипеды вроде QtCore имеют экспорт в stl-контейнеры
3. и в С и в С++ можно наделать костылей, но в последнем больше возможностей для этого
4. stl немного быстрее Qt ( по замерам namezys )
5. ahonimous признает, что тупил несколько раз - но на общий итог это не повлияло

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

>Да на С++ такое и никому в голову не придет

...в силу низкой квалификации

подсчитать размер объект и целиком его сразу разместить в памяти, а членом присваивать смещение

Где ты там увидел смещения? 100%-е чистые указатели.

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

во-первых, Вы то говорите об экономии памяти, то о скорости

Я говорю о них вместе. Экономится и память, и такты процессора.

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

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

Не вижу необходимости доказывать очевидный факт, что один вызов malloc быстрее, чем три вызова malloc.

откуда Вы взяли 3 malloc'a?

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

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

хотите чтобы было быстро - делайте cache concious

>в-третьих, запилите уже custom allocator и будьте счастливы, пусть он все эти вещи разруливает

существование сторонних cpp-библиотек, которые хотя std::string сильно усложняет жизнь. Так понятнее?

Вы гоните :)

>между тем обсуждаются обобщённые контейнеры

std::string назвать обобщенным контейнером язык не поворачивается,

тренируйте мышцы языка

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

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

Покажи мне оверхед!

Вы тред вообще читали? перечитайте ещё раз, он как раз этому посвящён

>постановка задачи некорректна, Вы что с чем здесь собрались конкатенировать?

Промежуточный результат с одним из аргументов функции. Даже прототип оператора написал, а ты все равно не понял?

да, я таким прототипом нихрена понять не могу - отсутствует возвращаемое значение, непонятно где Вы предполагали его поместить, я ведь Вам простой вопрос задал, но Вы и на него не можете ответить :)

>используется POD (char), размер типа данных у обоих контейнеров одинаковый, проверяете чтобы места хватило и memcpy :)

Ты правда такой непонятливый чтоли? Я тебе говорю, что такой operator= ты никак не добавишь, остается только наследоваться.

а что Вас смущает в таком развитии? наследование - вполне легальный механизм

и потом, вообще сделайте функцию осуществляющую преобразование и не плакайте

Мне уже начинает надоедать объяснять прописные истины.

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

Не провокация, а твое банальное неумение эффективно писать на C++

:lol: насмешил старика, не думал что такое от тебя услышу, пешы ищо, кросавчег

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

Да на С++ такое и никому в голову не придет

...в силу низкой квалификации

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

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

>ну да - 100% совместимости нет, я слишком обобщил, но приемов, которые могут позволить обойти С++ у С нет

А еще в C99 есть массивы переменной длины. Я уже даже не бью ногами мертворожденные iostream.

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

>Вы тред вообще читали? перечитайте ещё раз, он как раз этому посвящён

Вот я и спрашиваю тебя, где ты увидел оверхед? В GList чтоли?

да, я таким прототипом нихрена понять не могу - отсутствует возвращаемое значение, непонятно где Вы предполагали его поместить, я ведь Вам простой вопрос задал, но Вы и на него не можете ответить :)

Учись читать пояснения в скобках: вопрос как раз и состоял в том, что следует возвращаться из такого оператора: pooled_string или basic_string.

а что Вас смущает в таком развитии? наследование - вполне легальный механизм

Так ты не теоретизируй, ты попробуй скрестить два basic_string с разными аллокаторами, а потом уже говори.

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

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

Я смотрю, местные плюсоводы сравни лисперам: «одепты» ленивого программирования.

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

>> Да на С++ такое и никому в голову не придет

...в силу низкой квалификации

Ну канешно да ))) Правдо по сравнению и мнению ЛИСМеров вам Сишникам до квалификации тоже ищё далеко. Учи ЛИПС приближайся к иёлите )))

подсчитать размер объект и целиком его сразу разместить в памяти, а членом присваивать смещение

Где ты там увидел смещения? 100%-е чистые указатели.

Всеьаки похоже ты тормоз ещё тот. Давай по порядку.

1. Ты выделяешь память для всей структуры разом. Нет?

struct Person *p = (struct Person*)malloc(sizeof(struct Person*) + nick_l + name_l + 2);

2. И если у тебя правильно легла строка здесь «memcpy(p->name, name, name_l + 1);» То это нах не твоя заслуга, а компилятора. Значить он что-то за тебя наверно посчитал и зделал хорошо.

Мне на самом деле твой код кажется очень сомнительным. Я его не тестил. Но типа ты его так упорно здесь запихнул - я поверил тебе на слово, что он рабочий.

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

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

> А еще в C99 есть массивы переменной длины.

int* a = alloca( sizeof( int ) * x );

alloca это просто 6 строк на ассембере, которые инлайнятся в код, можно даже макрос написать

Я уже даже не бью ногами мертворожденные iostream.


тут не буду спорить

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

> alloca это просто 6 строк на ассембере

сразу оговорю - не во всех реализациях их 6, чтоб не было придирок

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

> Пожалуй я посмотрю сейчас. )))

Твой код заработал канешна), но смещение от начала структуры за тебя посчитал компилятор и правильно положил строку. В чем я был не прав? В том что не написал, что за тебя это сделал копилятор? Я понимаю, что ты хотел нам явить мощь Си, но расскажи тогда как твой код поведет себя если будет сменен Ник (и его длина не будет совпадать/будет больше, чем у предыдущего значения). Расскажи нам тупым плюсовикам, что приключится в этом случае?)

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

>> О том, что в результате получается Mozilla Firefox я уже говорил.

Хром тоже на плюсах написан

Я его спрашивал на счет показать браузер написанный на Си, он помявшись ответил - Нету. Я спросил, а как он думает почему? на это он не нашел, что ответить.)

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

> а я о чем :) не переопределяйте new - и все будет хорошо

я знаю несколько очень интересных стратегий при переопределении new

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

> А еще в C99 есть массивы переменной длины.

Это которые callocated?

Я уже даже не бью ногами мертворожденные iostream.

А чем он вам не нравится?

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

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

При желании стратегии выделения памяти могут быть ОЧЕНЬ разнообразны

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

> При желании стратегии выделения памяти могут быть ОЧЕНЬ разнообразны

могут, а потом попробуй догадаться, что баг в конструкции «new Person», из-за того, что кто-то переопределил new, а так сразу будет видно - ага выделили память под пул, а вот пошло создание объектов в пуле

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

конечно, с этим надо быть осторожнее

Одна из стратегий:

создаем объекты memory manager: выделяют и освобождают память

переопределяем new/delete так, чтобы в выделенный кусок памяти записывался указатель на memory manager, с помощью которого выделялась память. в delete вызываем удаление именно из него.

Профит

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

>> Я его спрашивал на счет показать браузер написанный на Си

Вроде как midory

Да!) Но пациент про него не в курсе ))) И слава богу!

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

>Я уже даже не бью ногами мертворожденные iostream.

трахаешься с FILE? отсутствие потокового i/o - один из главных недостатков С в моих глазах

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

С++ умеет все то же, что и С, так что можно взять твое же решение 1:1

4.2.

struct {int a; int b; int c;} x = {.a = 5, .c = 18};

Если это самое большое отличие, то сиплюсплюсники могут спать спокойно.

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

>> Вы тред вообще читали? перечитайте ещё раз, он как раз этому посвящён

Вот я и спрашиваю тебя, где ты увидел оверхед? В GList чтоли?


Собственно, один из ключевых вопросов этой темы - возможно ли обойти совершенно бесполезный оверхед в GList средствами языка C.

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

> емнип, STL входит в стандарт, а значит это часть C++

То что буква S в STL расшифровывается как Standard не делает STL панацеей в C++. А тут некоторые пытаются говорить так, как будто в C++ кроме STL ничего больше и нет.

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

> они позорно умалчивают, что там WebKit - есть у него С API и ладно

Ага, явно фанатиги ООП на пюре Си делает ))) Не вопрос, что ядро на С++, главное идея ))) И кстати да, с точки зрения архитектуры, это на мой взгляд вообще полный маразм обвязывать плюсовый код сишным.

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

Собственно, один из ключевых вопросов этой темы - возможно ли обойти совершенно бесполезный оверхед в GList средствами языка C.

Нельзя. Если на Си нужно ввести какой-то сложный АТД, то нужно фактически создать `run-time' (не путать с интерпретатором АТД) для этого АТД - набор объектов (структур) и функций для работы с этими объектами. Все объекты будут выделяться в куче, динамическое разруливание нужно делать switch-ами по «аккессору» типа объекта, а классы типов достигаются бинарным соответствием первых полей структур.

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

То есть компилятор С++ более умный по части того что есть в С++, ну а в Си вручную введённые абстрактные сущности будут работать медленнее.

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

В С++ есть ряд АТД «вживлённых» в язык - компилятор знает о них и есть ряд оптимизаций связанных с техникой шаблонов.

Не точное выразился - средства введения АТД и оптимизации типа шаблонных.

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

Вы тред вообще читали? перечитайте ещё раз, он как раз этому посвящён

Вот я и спрашиваю тебя, где ты увидел оверхед? В GList чтоли?

ахаха, в glist :) в си! Вы представьте себе что сторонние либы (включая glib) Вам недоступны и нужно заимплементить glist - вот он, геморрой на земле

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

Учись читать пояснения в скобках: вопрос как раз и состоял в том, что следует возвращаться из такого оператора: pooled_string или basic_string.

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

>а что Вас смущает в таком развитии? наследование - вполне легальный механизм

Так ты не теоретизируй, ты попробуй скрестить два basic_string с разными аллокаторами, а потом уже говори.

УМВР ЧТЯДНТ?

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

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

Я смотрю, местные плюсоводы сравни лисперам: «одепты» ленивого программирования.

не знаю куда ты там смотришь, но у меня стойкое ощущение, что ты в серьёзных проектах не участвовал

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

ВНЕЗАПНО! У нас с вами разные memcpy()!

glibc-2.10.1/string/memcpy.c

/* Copy memory to memory until the specified number of bytes
   has been copied.  Overlap is NOT handled correctly.
   Copyright (C) 1991, 1997, 2003 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Contributed by Torbjorn Granlund (tege@sics.se).

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <string.h>
#include <memcopy.h>
#include <pagecopy.h>

#undef memcpy

void *
memcpy (dstpp, srcpp, len)
     void *dstpp;
     const void *srcpp;
     size_t len;
{
  unsigned long int dstp = (long int) dstpp;
  unsigned long int srcp = (long int) srcpp;

  /* Copy from the beginning to the end.  */

  /* If there not too few bytes to copy, use word copy.  */
  if (len >= OP_T_THRES)
    {
      /* Copy just a few bytes to make DSTP aligned.  */
      len -= (-dstp) % OPSIZ;
      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

      /* Copy whole pages from SRCP to DSTP by virtual address manipulation,
	 as much as possible.  */

      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);

      /* Copy from SRCP to DSTP taking advantage of the known alignment of
	 DSTP.  Number of bytes remaining is put in the third argument,
	 i.e. in LEN.  This number may vary from machine to machine.  */

      WORD_COPY_FWD (dstp, srcp, len, len);

      /* Fall out and copy the tail.  */
    }

  /* There are just a few bytes to copy.  Use byte memory operations.  */
  BYTE_COPY_FWD (dstp, srcp, len);

  return dstpp;
}
libc_hidden_builtin_def (memcpy)
Yareg ★★★
()
Ответ на: комментарий от linuxfan

Ну покажи пример того, как «у тебя все работает» :D

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

#include <allocators>
#include <algorithm>
#include <iterator>
#include <string>

template<typename _ty>
void append_to_string(std::string *target, const _ty& source) {
	std::copy(
		source.begin(), source.end(),
		std::back_inserter( (*target) ) );
}

int main(void) {

	typedef std::basic_string<
		char, char_traits<char>, 
		stdext::allocators::allocator_newdel<char> > custom_allocator_string_t;

	custom_allocator_string_t str_2(", bozo");
	std::string str_1("eat shit");

	std::cout << "1st: " << str_1 << ", 2nd: " << str_2 << std::endl;
	
	append_to_string(&str_1, str_2);

	std::cout << "result: " << str_1 << std::endl;

	return 0;
}

и да, сейчас доделываю виндовую часть проекта на работе, так что времени переключаться в Linux и проверять работоспособность кода там у меня нет, посему проверено на Windows 7 + msvc2010, в Linux могут быть нюансы, но чисто теологические

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