Pull to refresh

Comments 49

В русской литературе применяется слово «синонимы» для обозначения alias'ов.
Как раз щас диплом пишу на тему анализа указателей и синонимов, не знал что gcc умеет «strict aliasing».
Действительно лучше использовать термин «синоним», т.к. я сначала было решил, что речь пойдет о том, чтобы проводить сглаживание при помощи C++
Я встречал «псевдоним».
К тому же,
псевдоним = второе имя,
синоним = слово, близкое по значению.

Что логичнее?
В переведенной «Книге Дракона» используется как раз «псевдоним».
Как напишешь диплом, расскажешь на хабре? :)
Сначала надо написать :)
вроде полгода же всего до защиты осталось? ;-)

я слегка удивлен, что вам не сообщили в X, что GCC умеет достаточно продвинутый анализ синонимов делать: межпроцедурный, чуствительный к потоку управления, с поддержкой указателей на поля… первая версия у них уже в 99 году появилась, в gcc 2.x. Правда GCCшники о своей реализации скорее всего никаких статей не писали.

[впрочем и Lars Ole Andersen и Bjarne Steensgaard, и Heintze они свои анализы описывали для С или С-подобных языков, с операцией взятия адреса и т.д. Для Java все-таки чуточку проще в этом смысле… Ни указателей на поля, ни операции взятия адреса переменной. Berndl et al правда свой прикольный алгоритм с BDD для Java описали, но если я правильно понимаю ни в одном промышленном компиляторе он не используется до сих пор].
А я про вас слышал :)
надеюсь обо мне вспоминают с той же теплотой, с какой я вспоминаю родной X ;-)
Я так понял, cypok продолжил в магистратуре свою бакалаврскую тему диплома, и скоро у него опять будет защита. Может, на этот раз он поделится результатами своего плодотворного труда на хабре
Неожиданно! :) Да, orionll все правильно вычислил. В этом году удалось достичь существенного прогресса, диплом уже выглядит солидно и содержит несколько действительно новых идей. После написания текста серьезно планирую в каком-то виде выложить это на хабр. Но надо подумать над подачей, в дипломе появилось много математики и науки. :)
Ну что, дописал?
Дописать, дописал, но до публикации руки не доходят.
20 февраля будет годовщина первого комента :)
Отметили годовщину? =)
В профессиональной среде знакомых с явлением лиц, боюсь, слово «синоним» проще всего объяснить именно как «алиас» :) Плюс снимаются проблем неоднозначности «aliasing», «pointer aliasing» итп. Иначе «синоним указателя» например сразу имеет неоднозначный смысл — это «pointer aliasing» или таки «alias of a pointer» или еще что? Неясно.
> Компиляторы врут

Доктор Хаус переквалифицировался в программисты? :)
Это универсальная мудрость, применимая везде!
Удивительное рядом. Спасибо за интересную статью.
Отличная статья!

Мне всегда было интересно, как же поступать в случае, если надо обработать двоичный пакет, пришедший по сети. Предположим, есть буфер char buf[12], где хранится пакет, и описание того, что в нём должно быть:
struct packet {
  int a, b;
  char s[4];
};

Код типа const struct packet *p = (const struct packet *)buf; не прокатывает по вышеописанным причинам. Но, получается, всё будет хорошо, если buf скопировать в
union {
  char buf[12];
  struct packet p;
} u;

и далее работать с u.p? (При условии, конечно, что данные в пакете осмысленные и не являются trap representation.)
есть разные подходы.
1. передавать данные в текством виде например:
334 27 abcd
тут все просто — читаем два инта, и 4 символа
istringstream istr(str);
istr >> a >> b;
istr.read(s, 4);


второй вариант — бинарная передача. Тут чуть сложней
инты у нас как известно 32 бита. Но в коде это лучше уточнить как-нибудь так
int32 a,b;
Никаких лонгов, или типов указателей — они будут отличаться на 64х битных платформах от интов.
Далее — приводим их к сетевому byte-order
int32 a_network = htonl(a);
затем записываем в строку
char buf[sizeof(int32)] buf;
memcpy(buf, (char*)a_network, sizeof(int32));

тоже самое проделываем со вторым интом — тогда у нас для отправки есть 2*sizeof(int32) байт плюс четыре байта — буфер s

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

Третий вариант — не парится и использовать google-protobuf ( правда там тоже эта проблема не решена до конца, поскольку длину сообщения приходится тоже как-то передавать — что я обычно решаю передачей перед каждым сообщением 4х байт с длиной )

очень полезная и понятная статья, спасибо

В коде про сетевой (packet*) есть много более насущные грабли. Данные туда положит системный вызов и поэтому никаких проблем с перестановкой чтений-записи из-за SA не возникнет (если сразу сконвертировать указатель и работать только с ним).

Во1х сильно опаснее выравнивание полей (member alignment). Вероятность поймать «не такие» байтовые смещения полей куда выше, тк. компилятор поля выравнивает по границам 4/8/16 байт (и правильно делает, иначе доступ к памяти медленнее).

Во2х плюс пересылать целые числа по сети традиционно принято в отличном от Intel порядке байт, чтоб машины итп устройства с разными порядком могли друг с другом таки говорить. Это всякие маломодные устройства типа SPARC или несколько более частые ARM унутре iPhone, iPad итп мобил. Нужно делать ntohl()/htonl().
А для выравнивания есть "__attribute__((__packed__))" и "#pragma pack".
Хотя большую проблему в данном случае, имхо, создаст необходимость сначала считать данные в «упакованную структуру», а потом для скорости работы перекладывать их в «распакованный» вариант. Проще уже тогда элементы структуры читать поштучно. Ну или использовать упомянутый protobuf.
Основная мысль такая. Такие фокусы либо вообще не важны, либо неоправданно опасны.

Ну те. если среда сборки и исполнения всегда одинаковая вплоть до версии компилятора, то можно смело писать вообще (packet*) и оно даже будет работать, несмотря.

А если нет, то отличия трюков с упаковкой на разных платформах это потенциальное место для возникновения глупых проблем. Зачем такое место создавать, если можно сразу заткнуть, не очень понятно. При прочих равных предпочитаю тупой железобетонный код, который гарантированно нигде не сбойнет.
Месячник вкусных статей от shodan :)
Чёрт, никогда раньше не верил, что -O3 может так легко превратить код в кровавое месиво. Как только до сих пор не нарвался…
Везде рекомендуют использовать максимум -O2
Рекомендуют, но поясняют только тем, что «оптимизации на -O3 опасны», подкрепления реальными причинами до сего момента не встречал. И до сей поры, когда собирал свой код с -O3, единственная проблема, которую встречал — это в одной библиотеке в некоторых ревизиях порой возникает уход в вечную компиляцию, причём исключительно на amd64.
Спрятавшийся алиасинг это довольно тонкий баг, случается не каждый год (буквально), этим как раз имхо и интересен.

Про лютую, бешеную опасность это схоластика, конечно. Эдак можно договориться, что любые оптимизации опасны. Я и при -O1 ловил всякое. Это не более, чем лишний повод аккуратнее проверять и внятно бенчмаркать перед принятием того или иного решение.
Февраль выдался богатый на удивлятельные грабли. На месячник мне понадобится помощь коммьюнити в формате «расскажите мне, о чем рассказать» однако.
У ICC есть несколько опций управляющих работой дизамбигуатора (той части кода компилятора, которая решает как относятся друг к другу разные поинтеры).
По умолчанию считаем что все плохо.
-ansi-alias — прилада написана по стандарту С99
-fno-alias — считаем что все что явно не алиаснуто свободным для оптимизаций.
Честно говоря, я не понимаю тягу C/C++ прогеров к максимальной оптимизации всего и вся. Как правильно сказано в статье, для «нагруженных внутренних циклов» это действительно имеет смысл, но сколько таких циклов в общем коде программы? Конечно, это зависит от приложения, но например в обычной десктопной программе их будет очень мало. И нужно ли там экономить эти 50% от 10 милисекунд? Не это ли классический пример premature optimization?

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

В общем, я за хинты компилятору вроде __restrict там где это реально нужно, и против глобальных оптимизаций (таких как дефолтный -fstrict-aliasing). Так что я в каком-то смысле поддерживаю MSVC в этом вопросе :)
Я за точечную оптимизацию топа профайлера вообще-то. Это тоже нужно уметь делать. «Обычную десктопную программу» вообще писать на C++ сегодня не нужно. В необычных и недесктопных бывает и каждый 1% важен. Шаблоны опять-таки надо применять там, где уместно; а где неуместно, выкидывать.

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

Конкретно про aliasing мне лично (мне лично) все равно, включен он или выключен, главное, штоп warning был. Его местами однако нет и именно это более всего удручает.
Не нужно-то может и не нужно, но есть куча старых программ которые нужно поддерживать…
Другой вопрос ещё на чём писать, если не на плюсах — дотнет тоже не везде в тему.
А к сверхидее всё можно свести, главное побольше обобщить :)
Конечно, если всегда будет ворнинг то я ничего против не имею, но поскольку он есть не всегда — имхо уж лучше бы было выключено по умолчанию.
Я честно говоря не очень понимаю основного пойнта этой ветки обсуждения. О чем именно ты хочешь поговорить? Сверхидеи (см. «давайте будем богатыми и здоровыми») я так понял не годятся; дискуссиионного момента в них реально маловато, да. Хочешь пообсуждать частности, типа конкретных проблем парней, поддерживающих десктопный легаси софт, причем склонных к неуместным нано-оптимизациям? Ок, но я боюсь, на такую конкретную тему мне будет практически нечего сказать.

Насчет «как было бы лучше» я объективного мнения не имею. Общая личная позиция такая, что пацаны из gcc шибко теоретики, иногда в ущерб практике. Конкретно в этом случае пожалуй все равно.
Основной пойнт я обозначил в самом начале: дефолтный -fstrict-aliasing — по моему мнению зло. Не знаю уж, есть ли тут что обсуждать :)

Но вообще, на правах оффтопа было бы интересно узнать что ты имеешь в виду под «Обычную десктопную программу вообще писать на C++ сегодня не нужно». Я понимаю, конечно, что многие проекты сейчас выгоднее писать на .net, но если речь о чём-то нагруженном или кроссплатформенном или не обязательно требующем современной машины, то у меня мало идей что может подойти кроме плюсов.
А, ок. Просто заход с ламентаций про ненужные оптимизации итп шаблоны как-то смазал впечатление, что SA это именно основной!!!

Обсуждать конструктивно нечего хотя бы просто потому, что это в конце концов стандарт. Dura lex, sed lex, gcc в своем праве.

Для десктопа «в среднем» сегодня есть масса более приятных решений, чем лабать гуйню на, спаси господи, именно C++. Подозреваю, на любой конкретный случай жизни решение найдется. Подробно обсуждать сферический общий случай с постоянными отклонениями в частности (см. «а вдруг») не готов.
Я правильно понимаю, что фундаментально разные типы — это приводимые через reinterpret_cast? Классы в рамках одной иерархии считаются фундаментально разными?
Нет, не совсем. Для POD типов разрешен алиасинг при отличии квалификатора или знаковости (const int * супротив unsigned int * например), reinterpret тут ортогонален. К родительскому типу класса приводить тоже безопасно, конечно. Про дочерний однако не могу сказать наизусть, надо проверять; g++ -O3 -fstrict-aliasing -Wall в руки и вперед (на простейшем примере оно про type punning завсегда жалуется).
Нравится вот такое

THE RESTRICT CONTRACT
I, [insert your name], a PROFESSIONAL or AMATEUR [circle one] programmer recognize that there are limits to what a compiler can do. I certify that, to the best of my knowledge, there are no magic elves or monkeys in the compiler which through the forces of fairy dust can always make code faster. I understand that there are some problems for which there is not enough information to solve. I hereby declare that given the opportunity to provide the compiler with sufficient information, perhaps through some key word, I will gladly use said keyword and not bitch and moan about how «the compiler should be doing this for me.»

In this case, I promise that the pointer declared along with the restrict qualifier is not aliased. I certify that writes through this pointer will not effect the values read through any other pointer available in the same context which is also declared as restricted.

* Your agreement to this contract is implied by use of the restrict keyword ;)
Sign up to leave a comment.

Articles