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

★★★★★
Ответ на: комментарий от namezys
        default: 
            error ("Cannot print object: invalid type."); 

Какие ещё могут быть проверки - если в структуре Term *term есть подвохи, значит вообще есть косяки при его построении (ошибки), это internal структура (т.е. внутреннее представление работы каких-то функций API) - ей можно доверять.

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

Как мы уже убедились по словам (...) хороший код не выражается в строчках. То есть код на 200-300 строк того, что делает С++ за 5-6 лучше, потому что С это не такой монстр, как С++.

Да и зачем делать проверку типов? У нас все работает - проверенно

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

В С нет такого, и предполагается что не нужно. Можно писать имена функций с префиксами_ для типов, чем и будет достигнуто фактически то же самое (за вычетом удобства конечно).

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

а насчет статического полиформизка - спотрим на название функций в опенгл
WINGDIAPI void APIENTRY glTexCoord1d (GLdouble s);
WINGDIAPI void APIENTRY glTexCoord1dv (const GLdouble *v);
WINGDIAPI void APIENTRY glTexCoord1f (GLfloat s);
WINGDIAPI void APIENTRY glTexCoord1fv (const GLfloat *v);
WINGDIAPI void APIENTRY glTexCoord1i (GLint s);
WINGDIAPI void APIENTRY glTexCoord1iv (const GLint *v);
WINGDIAPI void APIENTRY glTexCoord1s (GLshort s);
WINGDIAPI void APIENTRY glTexCoord1sv (const GLshort *v);

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

> Да и зачем делать проверку типов? У нас все работает - проверенно

Да, если ты пишешь софт «на военную железку в которой нет С++» и знаешь, что его никто не будет за тобой править. Ну а если в большом проекте на С эмулируется ООП то «ООП костыли» настолько усложнят все дело, то тем кто будут разгребать это дальше останется только вешаться. Думается поэтому С++ стандарт для энтерпрайс.

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

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

Это увеличивает знание об этом коде

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

> а насчет статического полиформизка - спотрим на название функций в опенгл

«спотрим» и видим 8 разных функций, причем тут полиморфизм, особенно статический?)

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

увеличение знание о коде ? а некажеться ли что должен существовать предел НЕЗНАНИЯ о коде обьять все невозможно

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

-rw-r--r-- 1 root root 1,6M 2010-03-30 22:11 libcrypto.so.0.9.8

-rw-r--r-- 1 root root 1020K 2010-03-27 03:16 libstdc++.so.6.0.13

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

> а некажеться ли что должен существовать предел НЕЗНАНИЯ о коде обьять все невозможно

когда ты пишешь, ты должен думать, что пишешь, а не как пишешь

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

> вот ты видиш кролика - а ведь он есть !

Главное не видеть кролика там, где его нет) Ну это уже из области психиатрии.

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

да действительно

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

зачем чтото еще знать внутренне ? это же увеличит знание о коде - зачем ?

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

Это увеличивает знание об этом коде

Согласен. Я про то что принципиальной разницы нет (а то ведь вопрос поставлен «как? статический полиморфизм в си?», но отвечать на него нельзя ;)) Можно хакнуть компилятор, чтобы перед call совершался обход таблички и используя RTTI находилась нужная специальная функция (собственно, как в C++ и должно это работать), и фактически у нас всё равно много разных функций. Но удобней когда общий интерфейс, это да.

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

>и видел и отлаживал, поэтому Вас и спрашиваю что же там не так :)

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

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

а с чего вы взяли что програмист юзающий Си должен знать ваши Си++ определения правила и вообще

вот сказали конкретную задачу - я написал супер пупер код :)
так нет же - требуют какойто статический полиформизм - а что это такое ваще? и щачем это в этой задачи

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

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

Не кажется и вам, что это задача создателя класса объекта, и он должен научить это компилятор делать. А не заставлять меня помнить и смотреть все его changelog'и - а вдруг там с объектом что нибудь произойдет, и теперь побайтовое копирование станет невозможным

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

а зачем делать структуру данных (это не обьект - а СТРУКТУРА данных) которые бы это непозволяла делать

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

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

посмотри на clang - сообщение об ошибках вполне хорошие

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

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

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

> вот сказали конкретную задачу - я написал супер пупер код :)

он как раз не супер-пупер. Вот в этом проблема. Он даже решает другую задачу

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

открою страшну тайну - эти ваше полиформизмы ерунда перед задачем сделать на Си сложение двух строк :) вы эт и сами знаете

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

> а зачем делать структуру данных (это не обьект - а СТРУКТУРА данных) которые бы это непозволяла делать

Сначала была структура - потом она развилась в объект. Эволюция

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

нефига - была структоурой на Си - и структорой на Си и останеться

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

Вроде бы я сказал, что хотел

У меня есть обобщенный алгоритм в виде шаблона или макроса. В нем есть вызовы обобщенных функций (или методов или макросов). Эти обобщенных функций при подстановке параметров в обобщенный алгоритм должны определяться исходя из типа параметров

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

Ну это я тут путаюсь в С++ терминологии. Динамический полиморфизм использует RTTI для выбора метода (method для generic) в run-time, статический использует CTTI (т.е. внутреннюю информацию компилятора о типах - во время компиляции) для выбора метода во время компиляции (следуя вашим словам). А обобщённые алгоритмы это что? У меня это понятие (из С++) ассоциируются с параметрическим полиморфизмом, к которому в С++ имеют отношение шаблоны (но поборники Ъ языков тут могут начать катить бочку на С++ уже, пока тот медленно катится на си ;)

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

> необманывайте себя думая что С++ это делает както по другому

не обманываю. в хорошей библиотеки строк под С++ есть copy-on-write как минимум. обычно еще есть substring

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

Обобщенный алгоритм - это такая запись кода, поведения которого зависит от переданных объектов (их типов в частности)

В идеале хорошо, если обобщенный алгоритм полностью разрешится статически (без привлечения RTTI)

Кстати, для реализации дин. полиморфизма RTTI не используется - но это уже лирика

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

> паздравляю - вы таки делали конерктну задачу - или библиотеку для всего на свете ?

Я делаю алгоритм копирования массива - это ведь тоже задача.

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

а с чего вы взяли что програмист юзающий Си должен знать ваши Си++ определения правила и вообще

Не надо причислять С++ то что ему не пренадлежит, это не только его терминология (но и других языков сравнимого уровня).

вот сказали конкретную задачу - я написал супер пупер код :)

не, не ice ;)

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

Кстати, для реализации дин. полиморфизма RTTI не используется - но это уже лирика

Это не лирика, это пропаганда С++ архитекторов, которые приписывают понятию run-time type information какое-то узкое значение (у нас в африке когда в rt делается method dispatching, то обязательно используется какая-нибудь информация о типах - иначе ничего выбрать вовсе не выйдет).

Обобщенный алгоритм - это такая запись кода, поведения которого зависит от переданных объектов (их типов в частности)

Ну вот, это уже средствами си ну никак не получится - либо бустрап, либо мощный препроцессор (скорее всего бустрап).

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

В условиях ограниченности дин. полиморфизма языка С++ при его реализации можно обойтись без RTTI. Но это частный случай архитектуры С++. В obj-c это не так

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

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

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

да я со своей темой страдаю с соседнем топике - ну и сюда заглянул на огонек

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

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

У меня есть примеры веб-сервисов, которые сначала писались «не напрягая мозг» на це-пе-пе, а потом с минорным напряжением мозжечка на C. При этом 10-кратная разница в производительности и 5-кратная в потребляемой памяти. Не говоря уже об отсутствии ошибок и падений в сишном случае. И это все достигнуто исключительно благодаря тому, что в сишном случае я знал, что я пишу, а в плюсовом человек городил паттерны из умных книжек.

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

>и по вашему виноват язык, а не человек?

Виноват язык, который не поощряет использование мозга. Тред-то начался с чего: «а у меня в C++ все прозрачно: деструкторы вызываются, сложные структуры копируются, можно не париться со ссылками и указателями». О том, что в результате получается Mozilla Firefox я уже говорил.

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

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

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

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

Не понял, это вроде не моя цитата?) Ты кого имел ввиду?)

Тем, что это динамический полиморфизм.

Я не спорю, на С динамический полиморфизм организовать можно, но зачем изобретать велосипед если есть в системе С++ компилятор? Если ты пишешь это в целях самосовершенствования, for fun или для прокачки ЧСВ - это одно. А если ты лепишь это в коммерческий проект, это лишь будет означать одно - закончишь ты его (если закончишь) гораздо позже (а то и в разы) если бы писал на С++.

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

>Тут с алгоритмами уже что-то не то было, а не с автоматизацией языка.

Всего-навсего банально непонимание, что использовать std::map<std::string,std::string> для временного ассоциативного массива — ересь и пустая трата ресурсов.

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

Как выглядит разбор HTTP-заголовков на плюсовке: вычленяем имя заголовка, его значение и пихаем в std::map (минимум 3 malloc'a + 2 копирования строк). Как это выглядит на C: записываем в массив пару указателей, заменяем ':' и '\r' (или '\n') на 0. Вот тебе и 10-кратный выигрыш в производительности в одном из часто используемых мест в программе.

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

> У меня есть примеры веб-сервисов, которые сначала писались «не напрягая мозг» на це-пе-пе

Ключевое слово здесь «не напрягая мозг», а не «це-пе-пе» ))) Если бы плюсовик был бы нормальный, то работало бы не тормознее чем на си, но было бы гораздо компактнее и читабельнее. Не путай мух с котлетами )))

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