Показаны сообщения с ярлыком gcc. Показать все сообщения
Показаны сообщения с ярлыком gcc. Показать все сообщения

четверг, 13 января 2011 г.

Назначенные инициализаторы

Есть такая замечательная штука, как Designated Initializers. Эта возможность описана в стандарте ISO C99.

вторник, 28 июля 2009 г.

typeinfo for int

Загадочный всетаки компилятор - gcc...

Начать реализацию обработки исключений я решил в простенькой конструкции в коде:

try {
throw 1;
} catch (...) {
}

Но наткнулся на ошибку - undefined reference to `typeinfo for int'.
И вот возникла непонятная задача - как же мне описать typeinfo for int?

gcc очень загадочен. О существовании типа std::type_info он знает безо всяких дополнительных объявлений, что не мешает описать его локально. Но в gcc этот класс чисто базовый, констуктор у него защищен. На это можно было бы наверное наплевать и объявить все по своему, если бы в этом был смысл.

Но необходимый мне typeinfo должен иметь другой тип: __fundamental_type_info. И мне нужно проинициализировать его статически, для чего я долго изголялся с размещающими new в статическом буфере...

Стоит отметить что до сих пор линкер исправно ругался на отсутсвие typeinfo. Но после этого неожиданно начал ругаться на отсутвие 'vtable for __cxxabiv1::__pointer_type_info', странно.

Исследование модуля objdump'ом показало в нем наличие большого количества typeinfo. Стал выяснять - откуда они взялись. И вот ведь загадочный gcc...

Стоит вставить следующий код:

namespace __cxxabiv1 {
class __fundamental_type_info : public std::type_info
{
virtual ~__fundamental_type_info() { }
};
}

Как все необходимые фундаментальные typeinfo появляются как по волшебству. И с размещающим new я изголялся зря. :)

А с исключениями пока еще далеко не все понятно, разбираюсь.

пятница, 24 июля 2009 г.

Исключительная ситуация

Нет, у меня все в порядке, дела идут и жизнь легка. Просто лето и отпуск, и я совершенно забросил свой блог. Но делать так ни в коем случае нельзя. У меня уже был подобный опыт по забрасыванию рассылки comp.soft.prog.asmos на Subscribe.ru, которая находится в дауне до сих пор. Чем больше времени проходит с момента последнего поста, тем проще ничего не писать.

А темой сегодняшнего поста я избрал исключения. Но я не стану расскзывать тривиальные вещи, про то, что исключения из деструктора не бросатся, это и так все знают. Мне вдруг стало интересно как это работает изнутри. Но пока здесь одни вопросы. Разбираться надо.

gcc очень наглый компилятор. Он может спокойно инлайнить функции, даже если используется -O0. Он может нормальные казалось бы вызовы стандартных функций заменять ассемблерными мнемониками. Но с исключениями все обстоит еще сложнее. Стандартный, казалось бы, С++ код превращается в нагромождения непонятных вызовов c которыми я и хотел бы разобраться, чтобы прикрутить исключения к своему ядру.

Правда, тут все зависит от цены, так сказать. Патч для ядра linux, позволяющий использовать C++ с исключениями занимает 600к, в то время как вся моя кроссплатформенная часть (для которой собственно рунтайм и тащится) занимает 200к. Неравноценный получается обмен.

В самом gcc все реализовано максимально переносимо, из за чего, вероятно, трудно все это понять. Большой уровень косвенности. Мне же переносимость в данном случае не нужна, нужна минимальная реализация для конкретной платформы.

Так как же работают исключения? gcc, не смортя на всю свою наглость, конечно молодец. Для корректной отработки деструкторов выделенных в стеке объектов в нем используются так называемые "landing pad", область приземления? или как это правильно перевести. Область приземления располагается в конце каждого программного блока, в котором требуется деинициализация. Причем gcc очеь грамотно задвигает все, что не требуется деинициализировать.

Координаты этих областей перечисленны в "exception handling tables". Эти таблицы формируются компилятором для каждой функции. Хотя может и не для каждой, точно сказать пока не могу.

При возникновении исключения управление передается в функцию __cxa_throw, и дальше начинается сплошной грязный хак. Управление обратно уже не возвращается. Происходит разворот стека, для каждой функции производится поиск таблиц исключений, и в зависимости от адреса, где вызывалась функция __cxa_throw, выбирается ближайший landing pad и управление передается на него.

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

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

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

Так же для меня является загадкой, почему разработчики gcc так перемешали интерфейсы __cxa* и _Unwind*. Но в недрах реализации они переплетены столь плотно, что разорвать их практически нереально.

Если вдруг кто-то уже разбирался с обработкой исключений в gcc - отзовитесь, у меня много вопросов. :) Хотя постепенно осознание придет, если долго разбираться.

среда, 20 мая 2009 г.

Оптимизация в gcc, факты

При обновлении своей системы наткнулся на неприятный момент, сборка любых KDE компонент начала вдруг запарываться с руганью:
checking if UIC has KDE plugins available... no

Я далек от мысли что у всех все плохо в течении двух недель (где то так у меня впервые не собралась kopete), а Gentoo мантайнеры не чешутся. Поэтому стал разбираться откуда растут ноги.

Стандартной рекоммендацией в такой ситуации считается - пересобрать qt и kde, но легче от этого не стало. Первым делом я конечно заподозрил, что возможно нестабильную kde-3.5.10 сломали, снял все маски - жужжит (c). Потом я подумал, что, возможно, мешается qt:4, снес его, благо он был мне нужен только для perforce gui, можно потерпеть чуток, но счастья опять не наступило. Не наступило счастье и после полной пересборки системы, что уже совсем непонятно, уж полная пересборка должна помочь?

Долго гуглил... Не помню уже где, увидел упоминание про оптимизацию и gcc-4.3.2, а у меня вся система собрана на -Os. Решил поставить стандартный -O2, ради эксперимента. И, о чудо, компиляция компонентов kde прошла. На этом можно было бы остановиться, но кроме осознания факта неработоспособности qt3+kde3 собранных gcc-4.3.2 с опцией -Os, я узнал много нового про gcc, чем не могу не поделится.

Многие наверное будут удивлены, но опция -march=core2 не включает оптимизацию с применением sse*, как принято считать, а всего лишь допускает использование соответствующих встроенных функций. Как убедились, да очень просто:
void test()
{
// sse2 builtin
__builtin_ia32_movnti(0, 0);
}

$ gcc -march=pentium -c test.cpp
test.cpp: In function ‘void test()’:
test.cpp:3: ошибка: нет декларации ‘__builtin_ia32_movnti’ в этой области видимости
$ gcc -march=core2 -c test.cpp
$

Но в то же время использование правильной архитектуры вовсе не стимулирует компилятор к оптимизации кода с применением расширений. Как определили это? да очень просто:
$ gcc -march=core2 -Q --help=target
...
-msse [выключено]
-msse2 [выключено]
-msse3 [выключено]
-msse4 [выключено]
...

Для того чтобы эта оптимизация выполнялась - необходимо явно задавать -mssse3 -mmmx или другие, необходимые для вашей архитектуры.

Надо сказать, что конструкция gcc -Q --help=CLASS очень полезна во всех отношениях. Я раньше этого не знал. В качестве CLASS можно подставлять в частности target, optimisers, warnings тем самым анализируя настройки компилятора с используемыми в вашем проекте опциями.

PS: Одного я только не понимаю, как -Os может столь фатально влиять на связь приложения с библиотекой?

пятница, 26 сентября 2008 г.

deprecated: gcc vs vs vs vs...

vs Visual Studio... Опять наверное прописные истины, но хочу рассказать про аттрибут deprecated.

Аттрубут deprecated - это не только ценный инструмент для командной разработки (чтобы довести до других членов команды необходимость изменения API), но так же он незаменим при рефакторинге.

Я очень часто применяю его, если мне хочется что-то удалить, но предварительно нелишним будет узнать где это что-то используется. Тогда я это что-то объявляю как deprecated, и после компиляции точно знаю файлы и строки где это используется. Вовсе не обязательно удалять сразу, есть время подумать, подчистить и когда варнинги исчезнут - удалить окончательно.

Все, про рефакторинг больше ничего не скажу.

Хочется сравнить возможности атрибутов на gcc и visual с++. Поскольку атрибуты не входят в стандарт, то в каждом компиляторе это делается как попало.

Начнем с gcc. Для объявления чего-то устаревшим в нем используется конструкция __attribute__((deprtecated)). Её можно применять для переменных и типов:
int a __attribute__((deprtecated));
struct a_struct {} __attribute__((deprtecated));
struct { int a_field __attribute__((deprtecated)); };
void func(int a_arg __attribute__((deprtecated))) {}

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

Для функций:
void func() __attribute__((deprtecated)); // в прототипе
void __attribute__((deprtecated)) func() {} // в декларации

Причем если поставить атрибут не на место, то gcc будет ругаться грязно и непонятно насчет точек с запятыми, еще какой-то фигни.

В visual c++ дело обстоит так: указание аттрибута осуществляется с помощью __declspec(deprecated) и используется практически единообразно.


__declspec(deprecated) int a;
struct __declspec(deprecated) a_struct {};
// почему после struct то?
struct { __declspec(deprecated) int a_field; };
void func(__declspec(deprecated) int a_arg) {}
// игнорируется
__declspec(deprecated) void func();

В принципе схоже, свои заморочки, но их все перекрывает возможность указать сообщение.
__declspec(deprecated("use foo instead")) void func();
В gcc такая возможность только обсуждаются.

Правда как всегда в Microsoft - без ложки дегтя не обходится. Указал сперва сообщение по русски, но в Output увидел только квадратики... только два квадратика разного размера. Хотя в сообщении было явно больше двух букв...

суббота, 5 июля 2008 г.

Операции над указателями...

Долгая дискуссия заставила призадуматься...

С одной стороны в исходниках gcc я явно обнаружил обмен индекса и указателя, в случае операций с массивами (gcc/gcc/c-types.c):
 if (TREE_CODE (TREE_TYPE (array)) != ARRAY_TYPE
&& TREE_CODE (TREE_TYPE (array)) != POINTER_TYPE)
{
tree temp;
if (TREE_CODE (TREE_TYPE (index)) != ARRAY_TYPE
&& TREE_CODE (TREE_TYPE (index)) != POINTER_TYPE)
{
error ("subscripted value is neither array nor pointer");
return error_mark_node;
}
temp = array;
array = index;
index = temp;
swapped = true;
}
Но загадочности на этом не закончились. На самом деле компилятору действительно пофиг что с чем складывать. указатель с индексом или индекс с указателем, результатом всеравно будет указатель на элемент.
char s[] = "hello"; 
int idx = 3;
assert (*(s + idx) == 'l' && *(idx + s) == 'l');
Так вот второе выражение смущает меня больше всего.

Понятно что в математике от перемены мест слагаемых сумма не изменяется. Но и простым преобразованием этого объяснить тоже не получается. Попытка сложить два указателя вызовет ошибку компиляции. Модификация указателя всегда производится кратно размеру его элементов. В то время как модификация целого значения всегда кратна единице.

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

Вообще компилятор во многих случаях молчит. Я вот до сих пор считал, что удаление недостижимого кода - это оптимизация. И при компиляции без оптимизации компилятор должен тупо генерить все что написано. Но это оказалось не так.
int i = 10;
if (1) i += 20;
if (0) i -= 30;
$ gcc -O0 -S ...
        movl    $10, -8(%ebp)
addl $20, -8(%ebp)
add есть, а вот sub не наблюдаю...

Эта ерунда всплыла у меня когда я тшетно пытался посмотреть код, генерируемый выражениями argc + argv - 1, argv + argc - 1 с помощью BOOST_ASSERT(argc + argv - 1 == argv + argc - 1);. Выражение оказывалось истинным, и BOOST_ASSERT вообще не генерировал никакого кода, не смотря на -O0. Дык он и свертку выполнил безо всякого разрешения.

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

пятница, 15 февраля 2008 г.

Еще немного про in-charge конструкторы...

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

class foo;
class bar : public foo;
class cut : public bar;


Или как там обычно называют третий класс для примера?

Компилирую - каждый дочерний класс вызывает in-charge конструктор родительского, причем из обоих своих конструкторов! При этом идентичность двух версий конструкторов сохраняется.

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

0804855a <_ZN3barC2Ev>:
804855a: 8b 44 24 04 mov 0x4(%esp),%eax
804855e: c7 00 70 86 04 08 movl $0x8048670,(%eax)
8048564: c3 ret

08048584 <_ZN3barC1Ev>:
8048584: 8b 44 24 04 mov 0x4(%esp),%eax
8048588: c7 00 70 86 04 08 movl $0x8048670,(%eax)
804858e: c3 ret

Но нету никакой разницы.
Вероятно стоит с этим вопросом всетаки обратиться в comp.lang.c++, или наверное еще лучше к кому нибудь из группы gcc.

Может быть множественое наследование???
class foo;
class bar;
class cut : public bar, public foo;


Но нет, опять никакой разницы... что-то я наверное не понимаю :)
Может быть содержимого прикрутить? Опять не то.

А-а-а, ЭВРИКА! нашел, нашел... :)
В случае виртуального наследования конструкторы получаются разными!

class foo;
class bar : virtual public foo;

08048522 <_ZN3barC1Ev>:
8048522: 8b 44 24 04 mov 0x4(%esp),%eax
8048526: c7 00 fc 85 04 08 movl $0x80485fc,(%eax)
804852c: c3 ret

08048514 <_ZN3barC2Ev>:
8048514: 8b 44 24 08 mov 0x8(%esp),%eax
8048518: 8b 10 mov (%eax),%edx
804851a: 8b 44 24 04 mov 0x4(%esp),%eax
804851e: 89 10 mov %edx,(%eax)
8048520: c3 ret


not-in-charge конструктор _ZN3barC1Ev не обращает внимания на виртуальность наследования, в то время как in-charge конструктор _ZN3barC2Ev получает два параметра (два указателя this вероятно), и похоже переписывает указатель на vtbl из родительского класса в свой, хотя я не использовал виртуальные функции а данном эксперименте, но видимо виртуальное наследование автоматически приводит к появлению vtbl.

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

вторник, 5 февраля 2008 г.

not-in-charge конструкторы.

В тщетных попытках отучить gcc создавать копию конструктора зарылся в его исходники...

Как выяснилось, третий тип, complete object allocating constructor, никогда не используется. И на том спасибо, а то это была бы тайна трех конструкторов..

В терминологии gcc эти конструкторы называются in-charge и not-in-charge конструкторами. Разница между ними заключается в том, что not-in-charge конструктор конструирует только поля данного класса, не вызывая родительских конструкторов. В то время как in-charge конструктор конструирует объект полностью.

Использование в дочерних конструкторах not-in-charge конструкторов базовых классов позволяет избежать многочисленных инициализаций. Скорее всего это актуально для множественного наследования. Подробнее можно прочитать здесь.

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

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

Мне удалось избежать дублирования, описав тело конструктора в описании класса (inline конструктор), при этом он всеравно оформился в виде функции, но теперь уже однократно.

00000000 W _ZN15RandomGeneratorC1Ev

Вот пожалуй и все...

понедельник, 4 февраля 2008 г.

Разгадка тайны двух конструкторов.

К сожалению, пока только разгадка а не решение. И не понятно, почему я днем этого не обнаружил сразу, искал ведь.

gcc при генерации конструкторов, да и дестркуторов оперирует следующими понятиями: Полный конструктор объекта (complete object constructor), маркируется как C1. Этот конструктор вызывается при создании объекта данного класса; Конструктор базового объекта (base object constructor) - C2. Этот конструктор вызывается из конструкторов производных классов; Существует еще третий тип, как это по русски, полный выделяющий конструктор (complete allocating constructor), он маркируется как C3, но не знаю точно, в каких случаях используется.

Аналогичная ситуация и с деструкторами, почти... Деструктор удаления (deleting destructor) - D0, полный деструктор объекта (complete object destructor) - D1, деструктор базового объекта (base object destructor) - D2.

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

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

Мне надо придумать только как избавиться от лишней копии. Класс случайно не воспримет слово static?

Тайна двух конструкторов...

Опять обнаружил странное дело. Начал вносить в свое ядро c++ код, и сразу же новая загадка...

Надо сказать, что я не очень люблю загадки. Я предпочитаю иметь полный контроль над кодом. И когда происходит что-то, чего я не понимаю, я начинаю нервничать, проклинать тот день, когда я решил переписать ядро частично на c++, и вообще...

class RandomGenerator {
public:
RandomGenerator ();
...
};

Ничем не примечательный класс, однако в коде мы видим следующее...

001039ac T RandomGenerator::RandomGenerator()
001039cc T RandomGenerator::RandomGenerator()

Начинаем выяснять почему...

$ nm Kernel-IA32
...
001039ac T _ZN15RandomGeneratorC1Ev
001039cc T _ZN15RandomGeneratorC2Ev

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

$ objdump -d Kernel-IA32
Disassembly of section .text:

001039ac <_ZN15RandomGeneratorC1Ev>:
1039ac: 53 push %ebx
1039ad: 83 ec 10 sub $0x10,%esp
1039b0: 8b 5c 24 18 mov 0x18(%esp),%ebx
1039b4: 68 71 15 00 00 push $0x1571
1039b9: 53 push %ebx
1039ba: e8 6f fe ff ff call 10382e <_ZN15RandomGenerator4initEm>
1039bf: 89 5c 24 20 mov %ebx,0x20(%esp)
1039c3: 83 c4 18 add $0x18,%esp
1039c6: 5b pop %ebx
1039c7: e9 70 fd ff ff jmp 10373c <_ZN15RandomGenerator6reloadEv>

001039cc <_ZN15RandomGeneratorC2Ev>:
1039cc: 53 push %ebx
1039cd: 83 ec 10 sub $0x10,%esp
1039d0: 8b 5c 24 18 mov 0x18(%esp),%ebx
1039d4: 68 71 15 00 00 push $0x1571
1039d9: 53 push %ebx
1039da: e8 4f fe ff ff call 10382e <_ZN15RandomGenerator4initEm>
1039df: 89 5c 24 20 mov %ebx,0x20(%esp)
1039e3: 83 c4 18 add $0x18,%esp
1039e6: 5b pop %ebx
1039e7: e9 50 fd ff ff jmp 10373c <_ZN15RandomGenerator6reloadEv>

Они полностью идентичны за исключением смещений. Загадка однако. Стоит отметить, что других конструкторов тоже два.

0010388c T RandomGenerator::RandomGenerator(unsigned long const*, unsigned long)
001039ec T RandomGenerator::RandomGenerator(unsigned long const*, unsigned long)

Как с этим бороться?