Strict aliasing C/C++
самое главное, что тебе надо знать про strict aliasing - это опция -fno-strict-aliasing
самое главное, что тебе надо знать про strict aliasing - это опция -fno-strict-aliasingНе всё так просто же? Это не панацея, и компилятор не такой умный, чтобы указать на все такие места.
Все примеры, которые я привёл, прекрасно компилируются в этой опцией. Значит всё ОК, по-твоему?
Это не панацея, и компилятор не такой умный, чтобы указать на все такие места.Он же с этим ключом просто не будет полагаться на то что какие-то указатели ссылаются на различное место в памяти, т.е. не будет делать оптимизаций, связанных с strict aliasing.
а ты наверно подумал про -Wstrict-aliasing
Ага, перепутал с -Wstrict-aliasing, прошу прощения. Но отключать оптимизацию нельзя. Хочется писать правильный код, а для этого надо понять эту тему.
Но отключать оптимизацию нельзячто действительно нельзя делать, так это выключать вибратор, оказавшийся в анусе, ну а если ты подумаешь его не дай боже вынуть, знай - в мире мигом умрет не менее 100 котиков
Но отключать оптимизацию нельзяСпасибо, всё сразу же стало на свои места.
что действительно нельзя делать, так это выключать вибратор, оказавшийся в анусе, ну а если ты подумаешь его не дай боже вынуть, знай - в мире мигом умрет не менее 100 котиков
Ну а если по делу, то зачем выключать оптимизацию, если ты понимаешь, что не нарушаешь никаких правил.
Скажите, пожалуйста, во всех ли случаях strict aliasing нарушается, и в каком месте конкретно.мне кажется, что проблема с алиасингом только в примере 3
как я представляю себе проблему: если мы имеем пару (для простоты) переменных несвязаннного типа (long, int в данных примерах) и идет запись в одну переменную (или обе) и чтение из другой переменной, то поведение будет неопределено из-за нарушения алиасинга (когда по факту обе переменные пересекаются в памяти)
еще надо учесть, что char* может алиаситься с любым типом
вот тут алиасинг нарушен:
void* buf = malloc(8);
int* a = (int*)buf;
long* b = (long*)buf;
//main part:
*a = 8;
*b = 9;
printf("a=%d, b= %d", *a, *b);
на корректность не претендую, могу дать первую ссылку из гугла: http://dbp-consulting.com/tutorials/StrictAliasing.html и ссылка оттуда: http://www.cs.technion.ac.il/users/yechiel/CS/C++draft/ratio...
если мы имеем пару (для простоты) переменных несвязаннного типа (long, int в данных примерах)чем пара несвязанных переменных (int*, long*) отличается от пары таких же несвязанных переменных, но с типами (void*, int*)?
чем пара несвязанных переменных (int*, long*) отличается от пары таких же несвязанных переменных, но с типами (void*, int*)?не понял твой вопрос, уточни плиз
void* - это указатель на данные, но данные изменить нет возможности (надо перейти к другому типу)
вся тема об изменении данных
void* - это указатель на данные, но данные изменить нет возможности (надо перейти к другому типу)Что будет если добавить передачу void* переменной в другую функцию, где она уже и преобразуется к другому типу?
Вопрос, на который я не могу ответить - это каковы эти указатели: в одной функции, области видимости или ещё что-то. Ведь иначе malloc/new не работали бы, так как, очевидно, разные типы данных могут указывать на одну и ту же область памяти. Поэтому мне и хотелось бы отличить пример 4 от предыдущих трёх.
Да, замечание по поводу char абсолютно верно. Этот тип может алиасится с любым других. Поэтому, к примеру, memcpy не имеет проблемы с алиасингом, насколько я понимаю.
Да, по сути второй пример это иллюстрирует. Можно ввести множество модификаций. И я не понимаю, валиден ли такой подход или нет. Опять же, алиасинг - это когда два указателя указывают на одну и ту же область памяти. И мне не понятно, есть ли какое ограничение, когда эти указатели становятся 'далёкими друг от друга', и алиасинга нет. Или это распространяется на контекст всей программы.
#include <stdlib.h>
#include <stdio.h>
inline void int_buf(void *ptr)
{
int *int_ptr = (int *)ptr;
*int_ptr = 0;
return;
}
inline void long_buf(void *ptr)
{
long *long_tr = (long *)ptr;
*long_ptr = 1;
return;
}
int main
{
void *buf;
buf = malloc(8);
if (buf == NULL)
return -1;
int_buf(buf);
printf("int = %d\n", *(int *)buf);
long_buf(buf);
printf("long = %ld\n", *(long *)buf);
free(buf);
return 0;
}
Насколько я понял, имеет в виду следующий код. Есть ли тут проблема?я не вижу здесь проблем (возможные проблемы с выравниванием оставляем за скобками)
я не вижу здесь проблем (возможные проблемы с выравниванием оставляем за скобками)Я тоже не вижу, а она есть (с)
В действительности, я специально сделал функции inline, поскольку, тогда это не отличается от случая на мой взгляд.
Строго говоря, алиасинг присутствует int_ptr и long_ptr указывают на одну и ту же область памяти, но имеют разный тип.
Строго говоря, алиасинг присутствует int_ptr и long_ptr указывают на одну и ту же область памяти, но имеют разный тип.алиасинга здесь не вижу, потому что переменные имеют непересекающиеся времена жизни. они указывают на одну и ту же память в разные моменты времени
алиасинга здесь не вижу, потому что переменные имеют непересекающиеся времена жизни. они указывают на одну и ту же память в разные моменты времениМой контраргумент: компилятор видит, что чтения из int_ptr и long_ptr нет, а только присваивание, и просто выбросит эти блоки при оптимизации.
If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined
То есть в каждый момент времени каждый адрес памяти имеет какой-то один конкретный тип (с оговорками) и через указатель другого типа туда лезть нельзя.
Это разрешает кучу разных оптимизаций.
Мой контраргумент: компилятор видит, что чтения из int_ptr и long_ptr нет, а только присваивание, и просто выбросит эти блоки при оптимизации.какие именно блоки выбросит? там, где присваивание? их выбросить нельзя, т.к. туда смотрит переданный ptr и программа напечатает мусор при такой "оптимизации".
P.S. То есть во всех других случаях aliasing есть?
какие именно блоки выбросит? там, где присваивание? их выбросить нельзя, т.к. туда смотрит переданный ptr и программа напечатает мусор при такой "оптимизации".Да, блоки, где присваивание.
Вот здесь разбирается точно такой же пример, по-моему мнению.
P.S. Правда у меня (g++ 4.6.3) не воспроизводит результат.
У слова location здесь нет никакого специального смысла, просто область памяти.
переставитьЕсли бы имели что-то типа вот этого
{
int *int_ptr = (int *)buf;
*int_ptr = 0;
printf("int = %d\n", *int_ptr);
}
{
long *long_ptr = (long *)buf;
*long_ptr = 1;
printf("long = %ld\n", *long_ptr);
}
То компилятор мог бы переставить блоки местами.
Значит, примеры 1-3,5 содержат проблему!
У слова location здесь нет никакого специального смысла, просто область памятиА что, если malloc возратит ту же саму область памяти во второй раз, что и в первый.
Получается, два указателя разных типов ссылаются на одну и ту же область.
Проблема случается когда в память пишется один тип, а читается другой. В примерах память переиспользуется для хранения объектов разного типа, это нормально.
{
int *int_ptr = (int *)buf;
*int_ptr = 0;
printf("int = %d\n", *int_ptr);
}
Перенос printf внутрь блока ничего не меняет, int_ptr и (int *)buf очевидно совпадают. И вообще все изменения кода между 1-3,5 чисто косметические, это на самом деле один и тот же пример.
А что, если malloc возратит ту же саму область памяти во второй раз, что и в первый.Так не одновременно же. Первый указатель нельзя использовать после free - даже если ты убедился что он равен второму.
Получается, два указателя разных типов ссылаются на одну и ту же область.
весь код в этом треде корректныйя с тобой не согласен. я по-прежнему считаю, что в примере 3 от ТС проблема с алиасингом. в этом примере компилятор не переставит присваивания в int_ptr и long_ptr только если будет знать, что они алиасятся.
вдобавок, я приводил некорректный код
в обоих примерах компилятору не составит труда выявить алиасинг, но с точки зрения стандарта есть нарушение, ведущее к UB
Вот здесь разбирается точно такой же пример, по-моему мнению.отличие этого примера от твоего в том, что там нет связующего void*, который алиасится со всем
Про 3 - не согласен.
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:
— the dynamic type of the object,
...
Покажи плз в какой строке программы нарушается это правило (или найди другое правило).
Покажи плз в какой строке программы нарушается это правилов этой строке идет доступ к записанному int через указатель на long:
*ptr = 1;повторю ссылку http://dbp-consulting.com/tutorials/StrictAliasing.html , где в параграфе So what can alias поясняют, что несвязанные типы не алиасятся, что может привести к UB в случае реального пересечения по памяти
чуть выше такое:
Anything not on the list can be assumed to not alias, and compiler writers are free to do optimizations that make that assumption.в целом, описание в стандарте проблемы алиасинга мне не кажется кристально ясным
Далее, если ты будешь использовать char*, то все пять примеров будут ок по-моему, что наводит тебя на мысль об обратном?
Как бы strict aliasing означает что компайлер имеет право кэшировать значения твоих переменных в регистрах и не инвалидировать (статически) закэшированные значения после доступа via incompatible pointer type. То есть записать что-то куда-то через int* и достать через long* — не ОК, но в этом случае ты пишешь и достаёшь значения с одним и тем же типом, причём компилятор должен понимать что объект в обоих случаях алиазится с char* переменной и поэтому оба доступа транзитивно алиасятся. Мне так кажется. Но вообще вопрос интересный, что тебя на него натолкнуло?
Неее, запись не является "access the stored value". Иначе реюз памяти без деаллокации будет невозможен, как и всякие pool allocators, и union.
Неее, запись не является "access the stored value". Иначе реюз памяти без деаллокации будет невозможен, как и всякие pool allocators, и union.Что, почему? Для unions специально есть исключения в стандарте (зачем бы они были нужны, если бы они были не нужны? pool allocators use char*, что ты имеешь в виду под "реюз памяти без деаллокации"?
Но вообще вопрос интересный, что тебя на него натолкнуло?Да всё очень прозаично - просто реальный случай в проекте.
В моей программе есть компоненты, которые общаются "сообщениями". Сообщение представляет собой заголовок и данные. Существуют несколько типов сообщений. Все они имеют один и тот же заголовок, но данные разнятся. Я выделил единый кусок памяти, чтобы в него вмещались все типы сообщений, а также завёл несколько указателей. Указатель на заголовок (указывает на начало выделенного куска памяти указатели на данные (тип данных разный для разных типов сообщений которые все по сути указывают на одну и ту же область памяти (начало + смещение на величину заголовка). Весь процесс примерно выглядит следующих образом: некоторые функции заполняют данные заголовка (получая на вход указатель на заголовок) и данные (получая на вход указатель на данные а потом вызвают функцию 'отправки сообщений' (эта функция 'безопасна' с точки зрения алиасинга в том смысле, что рассматривате всё как массив char). Что-то типа этого:
char *buf = (char *)malloc(BIG_ENOUGH_SIZE);
header *header = (header *)buf;
msg_type1 *msg_type1_ptr = (msg_type1 *buf + sizeof(*header;
msg_type2 *msg_type2_ptr = (msg_type2 *buf + sizeof(*header;
fill_header(header);
fill_msg_type1(msg_type1);
send(buf);
fill_header(header);
fill_msg_type2(msg_type2)
send(buf);
etc
Всё работает (пока?) верно. Потом коллега обратил внимание на то, что указатели алиасятся. Мы начали думать - и понеслось...
Про "access the stored value" я тоже не совсем понял. Что означает "acess"? Чтение или запись?
Тогда в примере 3, как сказал , действительно, есть проблема, поскольку идёт запись по указателю long на то место, в которое до этого шла запись по указателю int. Но она, вроде как, есть и в предыдущих примерах 1-2.
Единственное, что может их отличать, это то, что в тот момент, когда идёт запись по указателю long, указатель int становится каким-то образом неважным, но я не понимаю это, честно говоря.
Говоря pool allocator я имел в виду любую бодягу которая реализует malloc/free не вызывая malloc/free, кешируя аллокации. Неважно как оно у себе внутри объявляет указатель на блок памяти. С точки зрения компилятора внутренности этого блока сначала используются для хранения одних данных, потом других. store A => load A => store B => load B. Точно как в примерах в первом сообщении.
P.S. При этом (write, type1 (read, char) валидны так как char алиасится со всем
Вот здесь чувак правильно объясняет со ссылками на Стандарт:
http://stackoverflow.com/questions/18624449/shared-memory-bu...
Оставить комментарий
kataich
Пытаюсь разобраться со strict aliasing в C/С++. Никак не могу понять чёткого определения, какие указатели действительно нарушают это правило. Ниже я приведу 4 примера. Скажите, пожалуйста, во всех ли случаях strict aliasing нарушается, и в каком месте конкретно.4 пример, кажется, совершенно верным, но что его отличает?
Update: 5 пример. Есть ли тут проблема?