LINUX.ORG.RU

type aliasing

 


0

3

Здравствуйте. Я тут недавно ковырялся с сокетами и столкнулся с такой ерундой: при передачи ipv4 сокета в байнд, необходимо выполнить преобразование из sockaddr_in* в sockaddr*

bind(sfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_in))
Две структуры не родственники и одна другую в себя не включает. Учитывая Type aliasing, правильно ли я понимаю, что чисто теоретически это невалидный код?
Но ведь это не говнокод какой-то, на что рассчитывал тот, кто это писал? Предположу, что расчётат на размещение bind в отдельном модуле (не допустить инлайна), следовательно не оставить ничего в регистрах перед вызовом. Что думаете? Конечно, можно не париться так сильно, ведь в документации в bind такой каст производится.
А вот пример из stl (на cppreference подобная техника демонстрируется, правда для <char>):
wifstream f("file");
double val;
f.read((wchar_t*)&val, 1); 
Почти наверняка внутри шаблона вызовется какая-нибудь функция ос, но это шаблон, сам он спокойно зайнлайнится, а его собственная реализация не очевидна для конечного пользователя.
К чему вообще так жёстко связывать ограничениями без предоставления простых обходных путей, что даже для реализации базовых компонентов эти правила активно нарушаются.

★★

Это весьма типичный сишный говнокод. Расчет идет на то, что sockaddr резервирует достаточно места для всех «производных» структур.

m0rph ★★★★★
()

Там два возможных случая: либо тип будет возвращён перед использованием и никаких проблем вообще нет, либо идёт обращение только к первым полям, которые идентичны у обоих структур, а значит это также разрешено и не нарушает type aliasing. Если внутри bind() нет именно обращения к полям, которые нарушают правила, то никакого нарушения нет.

xaizek ★★★★★
()

чисто теоретически это невалидный код?

Это С, детка.

Структуры sockaddr имеют одинаковое первое поле, short sa_family, насколько я помню. Как минимум есть структуры sockaddr_in для TCP/UDP-сокетов, sockaddr_un для UNIX-сокетов. А внутри bind уже разбирается как именно это дело биндить.

P.s. Просто глянь исходники bind, опенсорс же.

grondek
()
Последнее исправление: grondek (всего исправлений: 2)

Ну с точки зрения C++, невалидный выходит? Вырезка из стандарта:

[basic.lval]
8 If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

— (8.1) the dynamic type of the object,
— (8.2) a cv-qualified version of the dynamic type of the object,
— (8.3) a type similar (as defined in 4.5) to the dynamic type of the object,
— (8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— (8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— (8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
— (8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— (8.8) a char or unsigned char type.

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

Это весьма типичный сишный говнокод.

Это идиоматичный Си-код.

Расчет идет на то, что sockaddr резервирует достаточно места для всех «производных» структур.

Уровень понимания очень высок.

tailgunner ★★★★★
()

Учитывая Type aliasing, правильно ли я понимаю, что чисто теоретически это невалидный код?

IANAL, но код стал бы невалидным, если бы ты попытался обратится к sockaddr_in через указатель на sockaddr.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от m0rph

sockaddr резервирует достаточно места для всех «производных» структур.

sizeof(struct sockaddr) = 16
sizeof(struct sockaddr_in) = 16
sizeof(struct sockaddr_in6) = 28
sizeof(struct sockaddr_ipx) = 16
sizeof(struct sockaddr_un) = 110
sizeof(struct sockaddr_x25) = 18
i-rinat ★★★★★
()

Ну хорошо, что мы имеем? 1. С точки зрения Си вызов bind валиден (поверю участникам, сам не читал документация)
2. Гарантии написания bind() в .c у меня нет. Завтра реализацию функции перепишут так, что она будет в .h и при следующей перекомпиляции мой код сломается.
3. Ну и что делать пишущим на С++? Что скажете о таком решении:

flush_cpu_registers();
bind(..., reinterpret_cast<T1*>(T2*),  ...);  // Нарушаем type aliasing
flush_cpu_registers();

// где-то в каком-то .cpp 
void flush_cpu_registers() {}
Т.е. функция flush_cpu_registers() своя, объявлена в отдельном модуле (запрещаем инлайнить).

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

Это идиоматичный Си-код.

От этого он сильно лучше не становится.

Уровень понимания очень высок.

Показал свой высокий уровень знаний в данном вопросе? Да, здесь я ошибся. Общей частью с другими sockaddr_* структурами является только sa_family и еще небольшой участок памяти. Только вот i-rinat смог популярно показать мою ошибку, оставив полезный комментарий ниже, а ты бывает постишь только короткие фразы, цель которых видимо задеть или принизить оппонента.

m0rph ★★★★★
()
Последнее исправление: m0rph (всего исправлений: 2)
Ответ на: комментарий от pavlick

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

Пишущим на C++ использовать asio/QtNetwork/еще чего-нибудь или заткнуть паранойю и писать в C-стиле.

P.s. А чего ты паришься насчет регистров? Там же указатели передаются, а внутри скорее всего куча if и специализированных вызовов.

grondek
()
Последнее исправление: grondek (всего исправлений: 1)
Ответ на: комментарий от grondek

Наверное. Но не пойму одного - ну вот зачем надо было делать разное поведение у Си и С++ в таких базовы вещах. Если такой каст валиден в Си, то и В С++ должен быть валиден.

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

А разве при работе тупо с указателями без разыменования такой каст в C++ будет невалиден? Там же проблемы возникают именно при разыменовании.

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

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

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

Дело в том что этот код старше чем первый стандарт С++, поэтому это не код не постандарту а стандарт не по коду ...

zaz ★★★★
()
Последнее исправление: zaz (всего исправлений: 1)

родной, Си это вообще-то изначально ассемблер с наворотами для PDP-11. А C++ это ассемблер для PDP-11 c двойными наворотами.

Чего тебя здесь не устраивает? Есть гарантия от разрабов что твой sockaddr_X правильно сожрут потому что все знают, где будут лежать данные. И вообще похер что структуры не родственники. Процессор ведь не знает про структуры.

Да и насчет

double val;
f.read((wchar_t*)&val, 1); 
я тебе заранее говорю, что sizeof(wchar_t)<sizeof(double). иди и впредь не греши.

Это С++. Здесь ты можешь сделать всё. Он сделан чтоб не ограничивать, насколько это возможно, а не чтоб делать збс. Никакой страховки тут нет. Даже для программистов на Жаве. Даже если они будут вайнить на ЛОРе.

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

Здесь ты можешь сделать всё.

Даже если это и правда (хотя, очевидно, что нет), с каких пор это считается фичей?

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

При чём здесь процессор? Он будет жрать то, что ему даст компилятор. А компиляторописатели gcc говорят следующее:

-fstrict-aliasing
Allow the compiler to assume the strictest aliasing rules applicable to the language being compiled. For C (and C++), this activates optimizations based on the type of expressions. In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same. For example, an unsigned int can alias an int, but not a void* or a double. A character type may alias any other type. ... The -fstrict-aliasing option is enabled at levels -O2, -O3, -Os.

Не захочет компилятор и не скинет из регистра в память. Например (-O2):

void f88888888(int *i, short *s, int count){    //---# 1
    int *s2 = (int*)s;
    for(; count > 0; --count)   *i += *s2++;
}
__Z9f88888888PiPsi:
        pushl	%ebx
        movl	16(%esp), %eax      // eax = count
        movl	8(%esp), %ebx       // ebx = i
        movl	12(%esp), %edx      // edx = s
        testl	%eax, %eax
        jle	L1                  // if(count == 0) goto L1
        movl	(%ebx), %ecx        // ecx = *i
        .p2align 4,,7
L4:
        addl	$4, %edx            // ++ s
        addl	-4(%edx), %ecx      // ecx += *(s - 1)
        subl	$1, %eax            // -- count
        movl	%ecx, (%ebx)        // *i = ecx               !!! Сброс *i из регистра в память
        jne	L4                  // if(count > 0) goto L4  !!! конец цикла
L1:
        popl	%ebx
        ret

void f88888888(int *i, short *s, int count){    //---# 2
    for(; count > 0; --count)   *i += *s++;
}
        jne	L5                      !!! конец цикла
        movl	%edx, (%esi)            !!! Сброс *i из регистра в память
разное поведение обеспечено, если i и s указывают в одно место. Хотя логика одна и та же. Поэтому если пишут: «не кастуй так, нельзя», то лучше не надо.

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

Хотя логика одна и та же.

вообще-то не одна. просто ты не можешь в указатели. и в ассемблер походу тоже не можешь.

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

Хорошо. Кастуй, всё и во всё, удачного пути по граблям.

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

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

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

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

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

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

Равно как и плюсовикам которые верят в «Это С++. Здесь ты можешь сделать всё» :)

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

Я пишу на C и C++. Ответ был на твою конкретную реплику. Из нее следует, что ты не понимаешь strict aliasing. А это понимать надо. Тем более с таким ЧСВ

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

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

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

да лисп рулит

Это есть TRUE. Я был такой же упертый C++-шник. 100500 lines of code. Своего и чужого. Потом настал момент когда сражаться надоело. С языком, с кодом проекта, с дедлайнами. Так что не зарекайся и не ругай того, что пока не понимаешь, жизнь такая штука, может развернуться на 180 :)

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

C и С++ две БОЛЬШИЕ разницы. Я не противопоставлял C и lisp. C норм. С++ зло. Можно теоретизировать на эту тему, но кто участвовал в проектах на xxxxxxx строк C++ меня поймет.

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

Aliasing не такой свирепый как вы думаете. Он кусает только там где оптимизатор может пройти. А вызов библиотечной функции для него как memory barrier. Она автоматом считается лупящей по всей памяти

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

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

А «проекты на 100500» строк обычно говно потому что их говнари пишут

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

А «проекты на 100500» строк обычно говно потому что их говнари пишут

Это сильное мужское утверждение. Многое говорит о возрасте и опыте. Ну да и хрен с ним. Может дальнейшее прольет свет :)

I'm glad you asked.

Как говорится не ради кого-то убедить а для лулзов. И мне по фану седня. Положим ты программист высшей категории и устроилися в Microsoft или Google или еще куда. Тебе поставили задачу генерировать вместо 'foo', скажем '__foo__' для имени некоей сущности Symbol. Задача высунутого яйца не стоит в dynamic typing языках. Что ты делаешь в C++. Изучаешь иерархию классов, что как связано, да кто кого вызывает. Все равно нихрена не понятно, но ты (о чудо) нашел SuperMegaSymolsProvider::makeSymbol(). Правда, вот облом, он умеет добавлять два предшествующих подчерка, а последующие не умеет (aka __foo). Но ты идешь дальше. Наконец (после недель исследований) у тебя вырисовалась картина как можно добиться результата, через SymbolFactory и AbstactRenamingFacility и ты радостно идешь со своим кодом на 10 строк к начальству. А оно говорит - обратись к владельцу компоненты TheCorporateSymol, чтобы залить код. OK. Пошли к владельцу. Те тебе говорят что ты тупорылый идиот, НИКТО и НИКОГДА так не должен использовать SymbolFactory и AbstactRenamingFacility ТАКИМ ЕРЕТИЧЕСКИМ способом, и вообще мы звоним в полицию. И тп. Умножим все это на фактор того, что никто уже из живущих на планете Земля не помнит _первоначальный_ смысл AbstactRenamingFacility, _включая_ этих сумасшедших, «владельцев компоненты» и тд. ))). ___Для тех кто не спит — прошло три недели, чтобы сделать минутное дело___

Ну а начинающим поклонникам C++, бла-бла-бла OO штурмующим Микрософты и прочие Гуглы успеха!

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

Чтобы не вызывать панику :)

Если у вас стартап, nobody gives a f..k, какой у вас язык программирования, если вы в выгрыше.

Посмотрите исходники Quake1. Я так в школе писал.

The21century
()

Да, это нарушение как си, так и плюсов, правила у них почти одинаковы. Да, всем пофиг, потому что код снаружи и внутри bind слишком далеко друг от друга, чтобы что-то испортилось. ЕМНИП, для char касты валидны.

// Куча народу отписалась, вопрос поняло полтора человека. ЛОР.

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

Это не решение, это костыль, к тому же зависящий от компилятора.

Если тебе важна формальная корректность - то даже не предполагай ничего о регистрах и инструкциях, ибо в стандарте о них ни слова. А если нужен работающий на практике код, то просто не парься, он работает.

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

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

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

Функция bind описана в стандарте posix. Си тут вообще ни при чем. Ее можно хоть из питона дергать.

Для компилятора такие функции являются железобетонным memory barrier. В противном случае ты бы не смог передавать безопасно указатель на структуру у которой есть внутри еще указатели.

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

Более того, для того чтоб разрешить оптимизации вокруг вызовов функции, последнюю надо объявить как const или как pure. Вот последний режим как раз и снимет все барьеры, тогда как const снимет только read barrier после вызова функции

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

Что-то всё в кучу. Не в барьерах или const дело (барьеры ведь вообще о многоядерности). Запретить компилятору такую оптимизацию можно только одним способом - разместить функцию в отдельном модуле (.cpp или .c), такая функция не заинлайнится, будет выполнен полноценный вызов. А раз так, то компилятор не имеет никакой информации о том, как используются регистры в этой функции (сделаем asm вставку и забьём их нулями), значит компилятор будет вынужден произвести запись из регистров в память перед вызовом (и чтение из памяти после вызова). Кстати, volatile должна заставить всегда читать свежее значение в регистр. Я это всё как-то так вижу. По-хорошему, это всё должно быть отражено в стандарте и справочниках, а то правил навводили и сами нарушают.

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

У тебя тоже всё в кучу. Определись четко, чего хочешь, и найдешь четкий ответ.

Если нужен bind строго по стандарту, то union тебе в зубы. Брось костыли, встань и иди.

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

ну если совсем быть точным, то да это не барьер а скорее точка для компилятора, после которой всё должно быть точно в памяти. просто аналогия с «барьерами» наиболее проста для понимания.

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

В целом, более или менее понятно. Просто был несколько удивлён жёсткими правилами и запретами с одной стороны и тем, как на это всё кладут писатели linux api. На мой взгляд, проблемы не с api, а с правилами (неясность).
Спасибо участникам.

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

Просто был несколько удивлён жёсткими правилами и запретами с одной стороны и тем, как на это всё кладут писатели linux api.

Кладут все: http://kqueue.org/blog/2012/06/25/more-randomness-or-less/

А потом кто-то пишет компилятор, который с очередной версии начинает оптимизировать UB код. И только тогда начинают чесаться.

Так и здесь. Если действительно такое преобразование типа UB, то оптимизатор вполне может всегда возвращать 0, например. Причём только при определённой комбинации флагов оптимизации. И дальше, как повезёт.

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

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

Вот если кто-то, гипотетически, умудрится сделать bind (или его часть) не сисколлом... То всё равно он, зная про сложившуюся (плохую) практику, это учтет.

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

Задача высунутого яйца не стоит в dynamic typing языках
True story

От реальных людей из гоогле и микрософт я слышал истории про статику vs. динамику совершенно противоположные тем, что пытается тут выдать за true story это анонимное хуйло «The21century».

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

Указатель можно передать, конечно. Только толку от него, если >результат вычислений, которые должны находиться по адресу из >указателя, остались в регистре.

вот тут я ещё больше не понял чем в начале поста

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

Дай догадаюсь. Где-то тут я наверное должен костьми лечь и доказать тебе что-то, мой интеллигентный собеседник.

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