Будет ли это нарушение правила строгого сглаживания вести себя так, как я ожидаю?

Я знаю, что нарушение строгого правила псевдонимов является неопределенным поведением в соответствии со стандартом C. Пожалуйста, не говорите мне, что это УБ и тут не о чем говорить.

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

Предположим, что размер float и int составляет 4 байта, и это машина с прямым порядком байтов.

float f = 1234.567;  /* Any value here */
unsigned int u = *(unsigned int *)&f;

Мое ожидаемое поведение в английских словах: «получить четыре байта, где хранится float, и поместить их в int как есть». В коде было бы так (думаю тут нет УБ):

float f = 1234.567;  /* Any value here */
unsigned char *p = (unsigned char *)&f;
unsigned int u = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];

Я также приветствовал бы практические и конкретные примеры того, почему, помимо UB в соответствии со стандартом, компилятор может иметь то, что я считаю неожиданным поведением.


person atturri    schedule 22.04.2016    source источник
comment
Какой у Вас вопрос? Вы пытаетесь определить поведение неопределенного поведения? Ваше самое первое предложение уже говорит об этом ясно. Обратите внимание, что ваши смены также вызывают UB для определенных значений.   -  person too honest for this site    schedule 23.04.2016
comment
Некоторые компиляторы имеют определенное поведение в некоторых случаях c UB. Я хотел бы увидеть пример поведения компилятора, отличного от ожидаемого в этом случае.   -  person atturri    schedule 23.04.2016
comment
stackoverflow.com/questions/20762952/   -  person fukanchik    schedule 23.04.2016
comment
Вы должны спросить своего поставщика компилятора.   -  person Kerrek SB    schedule 23.04.2016
comment
@fukanchik: C и C++ — разные языки!   -  person too honest for this site    schedule 23.04.2016
comment
@ Олаф, ты говоришь, что memcpy не будет правильным ответом?   -  person fukanchik    schedule 23.04.2016
comment
@fukanchik, это хорошее чтение, но нет, это не ответ на вопрос. Я спрашиваю об этом точном коде, просто любопытство   -  person atturri    schedule 23.04.2016
comment
Почему бы вам просто не попробовать это (достаточно часто, так что вы можете быть уверены, что он всегда ведет себя так, как вы думаете, что должен, и делал это в первый раз :) )   -  person tofro    schedule 23.04.2016
comment
Я тщательно протестировал его в некоторых компиляторах с ожидаемым поведением. Но у мудрецов здесь могут быть контрпримеры или даже причины для другого поведения.   -  person atturri    schedule 23.04.2016
comment
Кроме шуток: большинство (чем старше, тем больше) будут вести себя так, как вы ожидаете. Некоторые не будут, некоторые прекратят это делать в своем следующем второстепенном выпуске. Какая польза от этих знаний? Что хорошего может быть в коде, который полагается на такие вещи?   -  person tofro    schedule 23.04.2016
comment
Не так давно я спустился в кроличью нору строгого псевдонима и нашел это. Он попадает в сборку и показывает, почему UB может произойти из-за нарушения строгого алиасинга.. если это то, о чем вы спрашиваете: dbp-consulting.com/tutorials/StrictAliasing.html   -  person yano    schedule 23.04.2016


Ответы (3)


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

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

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

person Chris Dodd    schedule 22.04.2016

float f = 1234.567;  /* Any value here */
unsigned int u = *(unsigned int *)&f;

Вот несколько вероятных причин, по которым это не сработает должным образом:

  1. float и unsigned int не одного размера. (Я работал с системами, где int составляет 64 бита, а float — 32 бита. Я также работал с системами, где int и float являются 64-битными, поэтому ваше предположение о том, что копируются 4 байта, не оправдалось.)

  2. float и unsigned int имеют разные требования к выравниванию. В частности, если unsigned int требует более строгого выравнивания, чем float, а f оказывается строго выровненным, чтение f как unsigned int может привести к плохим последствиям. (Это вероятно маловероятно, если int и float имеют одинаковый размер.)

  3. Компилятор может распознать, что поведение кода не определено, и, например, оптимизировать присваивание. (У меня нет конкретного примера этого.)

Если вы хотите скопировать представление float в unsigned int, memcpy() безопаснее (и я бы сначала проверил, что они действительно имеют одинаковый размер). Если вы хотите изучить представление объекта float, канонический способ сделать это — скопировать его в массив unsigned char. Цитирование стандарта ISO C (6.2.6.1p4 в N1570 черновик):

Значения, хранящиеся в объектах без битовых полей любого другого типа объекта, состоят из n × CHAR_BIT бит, где n — размер объекта. этого типа в байтах. Значение может быть скопировано в объект типа unsigned char [ n ] (например, с помощью memcpy); результирующий набор байтов называется представлением объекта значения.

person Keith Thompson    schedule 22.04.2016
comment
Я явно попросил предположить, что оба имеют длину 4 байта, а также думал о том, что они одинаково выровнены. Но хорошая точка зрения на 3 и интересная ссылка на стандарт. Спасибо. - person atturri; 23.04.2016

Вы вызываете неопределенное поведение без всякой причины.

Будет ли это нарушение правил строгого сглаживания вести себя так, как я ожидаю?

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

Это определило поведение, которое вы хотели бы:

union {
  float f;
  uint32_t i;
} ufi_t;
assert(sizeof(float) == sizeof(uint32_t);

ufi_t u = { 123.456 };
uint32_t i = u.i;

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

inline uint32_t int_from_float(float f) {
  ufi_t u = { f };
  return u.i;
}

Вы также можете безопасно перевести из (*float) в (*ufi_t). Так:

float f = 123.456;
uint32_t i = ((ufi_t*)&f)->i;

Примечание: языковые юристы могут разъяснить мне последнее, но это то, что я делаю с C9899:201x 6.5 и так далее.

person Kuba hasn't forgotten Monica    schedule 22.04.2016
comment
Ничто в стандарте не запрещает реализации налагать требование выравнивания для типов объединения, которое является более грубым, чем требование выравнивания для любого члена, содержащегося в нем. - person supercat; 09.01.2017
comment
@supercat Ага. И, ей-богу, много кода сломается, если кто-то это сделает :/ - person Kuba hasn't forgotten Monica; 09.01.2017
comment
Учитывая, что трюк union в общем случае не будет надежным, авторы компиляторов могут легко рассматривать разрешение на использование указателей типов struct и union как разрешение только на создание объектов без объявленного типа. написав их составляющие. Если некоторое хранилище без объявленного типа написано с int, правило позволит компилятору задним числом рассматривать его эффективный тип как объединение, содержащее этот int. Однако такое разрешение не сработало бы, если бы объект имел объявленный тип int. - person supercat; 09.01.2017