Почему строгое правило псевдонимов не применяется к int * и unsigned *? - PullRequest
0 голосов
/ 16 сентября 2018

В языке C мы не можем получить доступ к объекту, используя выражение lvalue, которое имеет несовместимый тип с эффективным типом этого объекта, поскольку это приводит к неопределенному поведению.И на основании этого факта строгое правило псевдонимов гласит, что два указателя не могут псевдонимы друг друга (ссылаются на один и тот же объект в памяти), если они имеют несовместимые типы.Но в p6.2.4 стандарта C11 разрешен доступ к действующему типу без знака с подписанной версией lvalue и наоборот.

Из-за последнего абзаца два указателя int *aи unsigned *b может иметь псевдонимы друг друга, и изменение значения объекта, указанного одним из них, может привести к изменению значения объекта, указанного другим (поскольку это один и тот же объект).

Давайте продемонстрируем это на уровне компилятора:

int f (int *a, unsigned *b)
{
    *a = 1;
    *b = 2;

    return *a;
}

Сгенерированная сборка вышеуказанной функции выглядит так в GCC 6.3.0 с -O2 :

0000000000000000 <f>:
   0:   movl   $0x1,(%rdi)
   6:   movl   $0x2,(%rsi)
   c:   mov    (%rdi),%eax
   e:   retq  

Что вполне ожидаемо, потому что GCC не оптимизирует возвращаемое значение и все еще читает значение *a после записи в *b (поскольку изменение *b может привести к изменению *a),

Но с этой другой функцией:

int ga;
unsigned gb;

int *g (int **a, unsigned **b)
{
    *a = &ga;
    *b = &gb;

    return *a;
}

Сгенерированная сборка весьма удивительна (GCC -O2):

0000000000000010 <g>:
  10:   lea    0x0(%rip),%rax        # 17 <g+0x7>
  17:   lea    0x0(%rip),%rdx        # 1e <g+0xe>
  1e:   mov    %rax,(%rdi)
  21:   mov    %rdx,(%rsi)
  24:   retq 

Возвращаемое значение оптимизировано, и ононе читать снова после записи в *b.Я знаю, что int *a и unsigned *b не являются совместимыми типами, но как насчет правила в параграфе P6.2.4 (разрешен доступ к действующему типу без знака с подписанной версией lvalue и наоборот)?Почему это не применимо в этой ситуации?И почему компилятор делает такую ​​оптимизацию в этом случае?

Есть что-то, чего я не понимаю в этой истории совместимых типов и строгого алиасинга.Может кто-то просветить нас?(И, пожалуйста, объясните, почему два указателя имеют несовместимые типы, но могут псевдонимы друг друга, подумайте о int *a и unsigned *b).

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

Чтобы понять предполагаемое значение подписанного / неподписанного исключения, необходимо сначала понять предысторию этих типов. Изначально язык 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, по-видимому, не продиктовано тем, что авторы Стандарта хотели сказать, как указано в опубликованном Обосновании, а скорее тем, что они не требуют от компиляторов.

0 голосов
/ 16 сентября 2018

Учитывая int **a и unsigned **b, тип *a не является типом со знаком или без знака, соответствующим действующему типу *b, и *b не является типом со знаком или без знака, соответствующим действующему типу.*a.Следовательно, это правило, разрешающее использование псевдонимов с помощью соответствующих типов со знаком или без знака, не применяется.Поскольку никакие другие правила, разрешающие псевдонимы, также не применяются, компилятор имеет право предполагать, что запись в *b не изменяет *a, и поэтому значение, записанное компилятором в *a в *a = &ga;, все еще присутствует в *aдля оператора return *a;.

Тот факт, что int * указывает на подписанный int, не делает его типом со знаком.Это указатель.int * и unsigned * являются указателями на разные типы.Даже если бы они считались подписанными или неподписанными, они бы указывали на подписанные или неподписанные указатели на разные типы: если бы int * был подписанным указателем, это был бы подписанный указатель на int, а соответствующая версия без знака была бы указателем без знака.на int, а не на указатель на unsigned.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...