Приведет ли арифметика указателей к членам объединения к алиасингу, зависит от того, как в конечном итоге будут использоваться указатели. В реализациях, которые дополняют Стандарт с гарантией, что правила «доступа к типам» будут применяться только в случаях, когда существует фактическое псевдонимание, или (для C ++) в случаях, связанных с типами с нетривиальной семантикой, допустимость операций с указателями будет иметь мало общего с тем, выполняются ли они над активными или неактивными членами.
Рассмотрим, например:
#include <stdint.h>
uint32_t readU(uint32_t *p) { return *p; }
void writeD(double *p, double v) { *p = v; }
union udBlob { double dd[2]; uint32_t ww[4]; } udb;
uint32_t noAliasing(int i, int j)
{
if (readU(udb.ww+i))
writeD(udb.dd+j, 1.0);
return readU(udb.ww+i);
}
uint32_t aliasesUnlessDisjoint(int i, int j)
{
uint32_t *up = udb.ww+i;
double *dp = udb.dd+j;
if (readU(up))
writeD(dp, 1.0);
return readU(up);
}
Во время выполнения readU никакое хранилище, к которому осуществляется доступ через *p
, не будет доступно никаким другим способом, поэтому во время выполнения этой функции не будет псевдонимов. Точно так же при исполнении writeD
. Во время выполнения noAliasing
все операции, которые будут влиять на любое хранилище, связанное с udb
, выполняются с использованием указателей, которые являются производными от udb
и явно имеют активные времена жизни, которые явно не перекрываются, поэтому здесь нет псевдонимов.
Во время выполнения aliasesUnlessDisjoint
все обращения выполняются с использованием указателей, производных от udb
, но доступ к хранилищу осуществляется через up
между созданием и использованием dp
, а доступ к хранилищу осуществляется через dp
между созданием и использованием up
. Следовательно, *dp
и *up
будут псевдонимами во время выполнения aliasesUnlessDisjoint
, если только udb.ww[i]
и udb.dd[j]
не занимают непересекающуюся память.
Обратите внимание, что и gcc, и clang применяют правила доступа к типам даже в случаях, подобных описанной выше функции без алиасинга, где нет фактического сглаживания. Несмотря на то, что в Стандарте явно указано, что выражение lvalue в форме someArray[y]
эквивалентно *(someArray+(y))
, gcc и clang разрешат надежный доступ к элементам массива в объединении только при использовании синтаксиса []
. Например:
uint32_t noAliasing2(int i, int j)
{
if (udb.ww[i])
udb.ww[j] = 1.0;
return udb.ww[i];
}
uint32_t noAliasing3(int i, int j)
{
if (*(udb.ww+i))
*(udb.dd+j) = 1.0;
return *(udb.ww+i);
}
Хотя код, созданный gcc или clang для noAliasing2
, будет перезагружен udb.ww[i]
после операции на udb.dd[j]
, код для noAliasing3
- нет. Это технически допустимо в соответствии со стандартом (поскольку правила в том виде, в котором они написаны, не разрешают доступ к udb.ww[i]
при любых обстоятельствах!), Но это никоим образом не подразумевает какого-либо суждения со стороны авторов что поведение gcc и clang уместно в качественных реализациях. Глядя исключительно на стандарты, я не вижу ничего, что могло бы предположить, что какая-либо конкретная из noAliasing
форм должна быть более или менее действительной, чем любая другая, но программисты, рассматривающие возможность использования gcc или clang в режиме -fstrict-aliasing
, должны понимать, что gcc и clang относятся к ним по-разному.
person
supercat
schedule
14.09.2018
std::variant
вместо сырых объединений. - person Dmitry Sazonov   schedule 10.01.2018std::any
. Мне было интересно, можно ли реализовать facebook :: string без UB и без поведения, определенного реализацией. - person Oliv   schedule 10.01.2018