Когда я МОГУ нарушать правила псевдонимов?

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

предупреждение: разыменование указателя с символом типа нарушит правила строгого псевдонима [-Wstrict-aliasing]

Строка - это моя собственная строка, которая является POD. Этот код вызывается из C. S может быть int. Строка в значительной степени struct String { RealString*s; }, но содержит шаблоны и вспомогательные функции. Я делаю статическое утверждение, чтобы убедиться, что String является pod, имеет 4 байта, а int — 4 байта. Я также написал утверждение, которое проверяет, все ли указатели >= NotAPtr. Это в моей перегрузке new/malloc. Я также могу поместить это утверждение в String, если вы предложите

Учитывая правила, которым я следую (в основном эта строка представляет собой модуль и всегда имеет тот же размер, что и int), было бы хорошо, если бы я нарушил правила псевдонимов? Это один из немногих случаев, когда кто-то ломает его правильно?

void func(String s) {
    auto v=*(unsigned int*)&s;
    myassert(v);
    if(v < NotAPtr) {
        //v is an int
    }
    else{
        //v is a ptr
    }
}

person Community    schedule 19.03.2012    source источник
comment
Как быстро я могу ездить без штрафа за превышение скорости? Microsoft сделала что-то подобное с MAKEINTRESOURCE, но они тоже пишут компилятор и могут добавить расширение. Обычные смертные не могут. А в C++ они могли бы добавить перегрузку.   -  person Bo Persson    schedule 19.03.2012
comment
Вы находитесь на скользкой дорожке здесь. У вас действительно есть веская причина не создавать отдельные функции func_i и func_s?   -  person Per Johansson    schedule 19.03.2012
comment
@PerJohansson: я делал это в начале проекта. Теперь этой причины больше не существует. Поэтому я сделал 2 функции вместо этого. +1   -  person    schedule 20.03.2012
comment
Никогда нельзя нарушать правила псевдонимов, поскольку, будучи UB, он дает компилятору бесплатный билет на удаление или преобразование всей вашей программы в неузнаваемый. Не имеет значения, можем ли мы посмотреть на это и сказать: «Да, это будет работать на 99% платформ с некапризным компилятором»; Стандарт не требует существования такого компилятора.   -  person underscore_d    schedule 27.08.2016


Ответы (4)


Безопасный способ обращения с переменной как с двумя разными типами — превратить ее в объединение. Одна часть объединения может быть вашим указателем, а другая — целым числом.

struct String
{
    union
    {
        RealString*s;
        int i;
    };
};
person Mark Ransom    schedule 19.03.2012
comment
Технически объединение может не работать для каламбура типов, и если было хранилище для s, прочитанное из i, не определено (например, оно всегда может быть 0). Однако в этом случае имеет место строгое нарушение псевдонимов из-за странного приведения, а не из-за какого-либо «реального» нарушения, поэтому можно просто привести от указателя к int. - person Maciej Piechotka; 10.06.2012
comment
@BenVoigt: На самом деле чтение из члена union, отличного от последнего записанного, является неуказанным поведением, а не неопределенным. - person jamesdlin; 10.06.2012
comment
@james: Можете ли вы указать на раздел в Стандарте? Насколько я могу судить, чтение неактивного члена объединения включает (1) выражение доступа к члену, которое является lvalue, и (2) преобразование lvalue в rvalue. Поскольку объект, на который ссылается lvalue, не является объектом типа lvalue (и ни каким-либо подклассом), любая такая программа вызывает неопределенное поведение в соответствии с разделом 4.1 ([conv.lval]). - person Ben Voigt; 10.06.2012
comment
@BenVoigt, вам нужно помнить, что это чтение указателя, а не разыменование указателя. - person ; 10.06.2012
comment
@acidzombie24: доступ к членам также является lvalue. В String t; t.s = new RealString("literal"); auto v = t.i; последний оператор требует преобразования lvalue в rvalue с использованием типа (int), отличного от типа реально существующего объекта (RealString*). - person Ben Voigt; 10.06.2012
comment
@BenVoigt: я считаю, что все указатели имеют преобразование в int. Я знаю, что (int)some_ptr работает, но я знаю, что это reinterpret_cast, но не знаю, насколько это законно. - person ; 10.06.2012
comment
@acidzombie24: reinterpret_cast из указателя на целочисленный тип является допустимым и будет выполняться в оба конца, если целочисленный тип достаточно велик. Но я нигде не могу найти, что чтение неактивного члена союза такое же, как reinterpret_cast. - person Ben Voigt; 10.06.2012
comment
И я нет. Но я пытался сказать, что это форма преобразования ptr в int, поэтому она существует. Все еще туманно, если это не определено или нет. Я склоняюсь к определенной стороне. - person ; 10.06.2012
comment
@BenVoigt: Хм, возможно, меня перепутали с C, где стандарт C99 явно указывает это как неопределенное поведение (раздел J.1). - person jamesdlin; 10.06.2012
comment
Определите безопасный и обработайте переменную как два разных типа. Если вы подразумеваете, что Стандартный C++ допускает каламбур типов через unions, как это делает C99 (и которые ленивые компиляторы передают своим внешним интерфейсам C++), это совершенно неверно. Если - и это единственная интерпретация, которую я могу сделать - вы имеете в виду, что OP или кто-либо в той же ситуации должен использовать этот union для переинтерпретации int из указателя, т.е. читать из члена, отличного от (активного), последнего записанного в - вы советуете им сломать свой код и надеетесь, что компилятор не найдет способ оптимизировать указанную поломку наблюдаемым образом. - person underscore_d; 27.08.2016

memcpy полностью поддерживается. То же самое и с каламбуром на char* (например, вы можете использовать std::copy).

person Ben Voigt    schedule 09.06.2012
comment
Я не уверен насчет C++, но определенно в C99 функция memcpy гарантированно безопасна для преобразования типов только в тех случаях, когда операнд назначения имеет объявленный тип. Спецификации преодолевают множество проблем, чтобы предотвратить безопасное использование memcpy для копирования объекта одного типа в хранилище без объявленного типа с целью доступа к нему как к другому типу. - person supercat; 02.09.2016
comment
@supercat: я вижу, что string.h имеет следующее правило, которое, следовательно, применяется к memcpy: если в описании конкретной функции в этом подпункте явно не указано иное, аргументы указателя в таком вызове по-прежнему должны иметь допустимые значения, как описано в 7.1. 4. Что именно в 7.1.4 вы считаете здесь проблемой? И как бы вы получили хранилище без объявленного типа? И какое это имеет значение для описанного сценария, который int i; memcpy(&i, &s, sizeof i); ? - person Ben Voigt; 03.09.2016
comment
В C99 (и я не думаю, что это изменилось в C11), учитывая, что int x=1234; long *p = malloc(sizeof (long)); *p = 5678; memcpy(p, &x, sizeof x;); пытается прочитать *p как long, вызовет Undefined Behavior, даже если int и long имеют одинаковое представление, поскольку memcpy установит эффективный тип *p на int. - person supercat; 03.09.2016
comment
Фундаментальная проблема заключается в том, что для обеспечения хорошей производительности компилятору необходимо знать, какие вещи могут иметь псевдоним источника или назначения memcpy и memmove, но функции были определены в то время, когда компиляторам было все равно; следовательно, они не предоставляют средств указания типов. Проблему можно было бы решить, потребовав, чтобы эти функции предполагали наихудший псевдоним для источника и получателя, но добавив новые функции, которые позволяют программисту указать, известны ли типы источника и/или назначения или известно ли, что тип назначения соответствует неизвестный тип источника. - person supercat; 03.09.2016
comment
Вместо этого авторы Стандарта подстроили memcpy/memmove так, что в случаях, когда исходный тип известен, а целевой тип неизвестен, компилятор может предположить, что место назначения будет прочитано с использованием того же типа, который использовался ранее для записи исходного кода. . Это может быть полезно в тех случаях, когда допущение правильное, но это паршивое допущение, позволяющее сделать компилятору, учитывая, что одним из основных применений memcpy исторически было выполнение каламбура типов. - person supercat; 03.09.2016
comment
@supercat: Но использование memcpy для каламбура, когда есть объявленный тип, по-прежнему разрешено, верно? Кроме того, в C++ нет такого эффективного типа объекта, из которого значение копируется ерунда. - person Ben Voigt; 03.09.2016
comment
Использование memcpy для преобразования типов разрешено в случаях, когда назначение имеет объявленный тип. Что касается C++, он указывает, что memcpy работает так же, как и в C, без явного указания, как он взаимодействует с динамическим типом используемого хранилища, что можно интерпретировать как делает поведение в отношении эффективного типа C применимым к динамическому типу C++. Тип. - person supercat; 03.09.2016
comment
Доступность Placement New устраняет многие трудности в C++, хотя было бы полезно иметь средства, указывающие, должен ли программист рассматривать хранилище как имеющее какой-либо набор битов, который он получил (какими бы то ни было средствами) до Placement New, или программист готов принять, что Хранилище может содержать Неопределенное Значение, незаразное Неопределенное Значение (повторные чтения дают независимые Неопределенные значения) или заразное неопределенное значение без перехвата (чтение дает Неопределенное значение) или, возможно, перехватывающее IV (чтение даст UB). - person supercat; 03.09.2016

Если вы не можете изменить код на 2 функции, как предложено, почему бы и нет (требуется компилятор C99, поскольку он использует uintptr_t - для более старых MSVC вам нужно определить его самостоятельно, 2008/2010 должно быть в порядке):

void f(RealString *s) {
    uintptr_t int = reinterpret_cast<uintptr_t>(s);
    assert(int);
}
person Maciej Piechotka    schedule 09.06.2012

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

Такой код, как ваш, должен быть пригоден для качественных реализаций, предназначенных для низкоуровневого программирования и представляющих вещи в памяти ожидаемым образом. Не следует ожидать, что его можно будет использовать в других типах реализаций, в том числе в тех, которые интерпретируют проблемы «качества реализации» как приглашение попытаться вести себя некачественно, но в соответствии с требованиями.

person supercat    schedule 13.07.2018