Чтобы понять предполагаемое значение подписанного / неподписанного исключения, необходимо сначала понять предысторию этих типов. Изначально язык C не имел целочисленного типа «без знака», но вместо этого был разработан для использования на машинах с двумя дополнительными компонентами с тихим циклом переполнения при переполнении. Хотя было несколько операций, в частности, реляционные операторы, деление, остаток и сдвиг вправо, где поведение со знаком и без знака будет отличаться, выполнение большинства операций со знаковыми типами даст те же битовые шаблоны, что и при выполнении тех же операций над типами без знака. Таким образом, сводя к минимуму потребность в последнем.
Несмотря на то, что неподписанные типы, безусловно, полезны даже на машинах с компоновкой с тихим переносом, они незаменимы на платформах, которые не поддерживают семантику с компоновкой тихого обхода. Однако, поскольку C изначально не поддерживал такие платформы, во многих кодах, которые по логике «должны», использовались неподписанные типы, и они использовали бы их, если бы они существовали раньше, вместо этого было написано, что они используют подписанные типы. Авторы Стандарта не хотели, чтобы правила доступа к типам создавали какие-либо трудности при взаимодействии между кодом, который использовал подписанные типы, потому что неподписанные типы не были доступны, когда он был написан, и кодом, который использовал неподписанные типы, потому что они были доступны, и их использование имеет смысл.
Исторические причины взаимозаменяемости трактов int
и unsigned
будут в равной степени применяться к доступу к объектам типа int*
с использованием lvalues типа unsigned*
и наоборот, int**
для доступа с использованием unsigned**
и т. д. Хотя в Стандарте прямо не указывается, что любое такое использование должно быть разрешено, он также не учитывает некоторые другие виды использования, которые, очевидно, должны быть разрешены, и поэтому не может быть разумно рассмотрено как полное и полное описание всего, что должны поддерживать реализации. .
Стандарт не проводит различий между двумя видами обстоятельств, связанных с указанием типа на основе указателя, - теми, которые связаны с наложением псевдонимов, и теми, которые не выходят за рамки ненормативной сноски, в которой говорится, что целью правил является указание когда вещи могут быть псевдонимами. Различие иллюстрируется ниже:
int *x;
unsigned thing;
int *usesAliasingUnlessXandPDisjoint(unsigned **p)
{
if (x)
*p = &thing;
return x;
}
, если x
и *p
идентифицируют одно и то же хранилище, псевдоним будет между *p
и x
, потому что создание p
и запись через *p
будут разделены конфликтующим доступом к хранилище с использованием lvalue x
. Однако, учитывая что-то вроде:
unsigned thing;
unsigned writeUnsignedPtr(unsigned **p)
{ *p = &thing; }
int *x;
int *doesNotUseAliasing(void)
{
if (x)
writeUnsignedPtr((unsigned**)&x);
return x;
}
не будет псевдонимов между аргументом *p
и x
, поскольку в течение времени жизни переданного указателя p
ни x
, ни любой другой указатель или значение, не производное от p
, не являются используется для доступа к тому же хранилищу, что и *p
. Я думаю, ясно, что авторы Стандарта хотели учесть последнюю модель. Я думаю, что не совсем ясно, хотели ли они разрешить первое даже для lvalue типа signed
и unsigned
[в отличие от signed*
или unsigned*
], или не понимали, что ограничивало применение правила случаями что фактически включает в себя псевдонимы, было бы достаточно, чтобы последние.
То, как gcc и clang интерпретируют правила наложения имен, не расширяет совместимость между int
и unsigned
до int*
и unsigned*
- ограничение, которое допустимо с учетом формулировки Стандарта, но которое - - по крайней мере, в случаях, не связанных с псевдонимами, я бы посчитал, что это противоречит заявленной цели Стандарта.
Ваш конкретный пример включает в себя псевдонимы в случаях, когда *a
и *b
перекрываются, поскольку либо a
было создано первым, и между таким созданием и последним использованием *a
возникает конфликт доступа через *b
,или b
был создан первым, и между таким созданием и последним использованием b
возникает конфликт доступа через *a
.Я не уверен, намеревались ли авторы стандарта разрешить такое использование или нет, но те же причины, которые оправдывают разрешение int
и unsigned
, будут в равной степени применяться к int*
и unsigned*
.С другой стороны, поведение gcc и clang, по-видимому, не продиктовано тем, что авторы Стандарта хотели сказать, как указано в опубликованном Обосновании, а скорее тем, что они не требуют от компиляторов.