Строгое правило алиасинга и указатели 'char *'

Принятый ответ на вопрос " Что такое строгое правило псевдонимов"? упоминает, что вы можете использовать char * псевдоним другого типа, но не другой путь.

Это не имеет смысла для меня - если у нас есть два указателя, один из типа char * и другой тип struct something * указывая на то же место, как это возможно, что первый псевдоним второго, а второй не псевдоним первого?

4 ответа

если у нас есть два указателя, один из типа char * и другой тип struct something * указывая на то же место, как это возможно, что первый псевдоним второго, а второй не псевдоним первого?

Да, но это не главное.

Дело в том, что если у вас есть один или несколько struct somethingс, то вы можете использовать char* читать их составляющие байты, но если у вас есть один или несколько charс, то вы не можете использовать struct something* читать их.

Формулировка в ссылочном ответе немного ошибочна, поэтому давайте сначала проясним это: один объект никогда не псевдоним другого объекта, но два указателя могут "псевдоним" одного и того же объекта (то есть указатели указывают на одну и ту же область памяти - как указывал MM это все еще не на 100% правильная формулировка, но вы получите идею). Кроме того, сам стандарт (насколько мне известно) вообще не говорит о строгом псевдониме, а только дает правила, с помощью которых можно получить доступ к типу выражений к объекту или нет. Флаги компилятора, такие как -fno-strict-aliasing, сообщают компилятору, может ли он предположить, что программист следует этим правилам (поэтому он может выполнять оптимизацию на основе этого предположения) или нет.

Теперь к вашему вопросу: любой объект может быть доступен через указатель на char, но char к объекту (особенно к массиву символов) нельзя получить доступ через большинство других типов указателей. Исходя из этого, компилятор может / должен сделать следующие предположения:

  1. Если тип самого объекта неизвестен, char* а также T* всегда может указывать на один и тот же объект (псевдоним друг друга) -> симметричные отношения.
  2. Если T1 а также T2 не "связаны" и не char, затем T1* а также T2* может никогда не указывать на один и тот же объект -> симметричные отношения
  3. char* может указывать на char ИЛИ T объект
  4. T* не может указывать на char объект -> симметричные отношения

Я считаю, что основное обоснование асимметричных правил доступа к объекту с помощью указателей заключается в том, что char массив может не удовлетворять требованиям выравнивания, например, int,

Таким образом, даже без оптимизации компилятора на основе строгого правила псевдонимов, например, написание int к расположению 4 байта char массив по адресам 0x1,0x2,0x3,0x4 приведет - в лучшем случае - к снижению производительности и - в худшем случае - к доступу в другое место в памяти, поскольку инструкции ЦП могут игнорировать два младших бита адреса при записи 4- значение байта (поэтому здесь это может привести к записи в 0x0,0x1,0x2 и 0x3).

Также имейте в виду, что значение "связанный" отличается от языка к языку (между C и C++), но это не относится к вашему вопросу.

если у нас есть два указателя, один из типа char * и другой тип struct something * указывая на то же место, как это возможно, что первый псевдоним второго, а второй не псевдоним первого?

Указатели не дублируют друг друга; это небрежное использование языка. Псевдоним - это когда lvalue используется для доступа к объекту другого типа. (Разыменование указателя дает значение l).

В вашем примере важен тип псевдонима объекта. Для конкретного примера скажем, что объект является double, Доступ к double разыменовывая char * указывать на двойник - это хорошо, потому что строгое правило псевдонимов это позволяет. Тем не менее, доступ к double разыменовывая struct something * не допускается (если, возможно, структура не начинается с double!).

Если компилятор смотрит на функцию, которая принимает char * а также struct something *и у него нет доступной информации об объекте, на который указывают (это на самом деле маловероятно, так как проходы сглаживания выполняются на этапе оптимизации всей программы); тогда это должно было бы учитывать возможность того, что объект на самом деле может быть struct something *, поэтому никакая оптимизация не может быть выполнена внутри этой функции.

Многие аспекты стандарта C++ являются производными от стандарта C, который необходимо понимать в историческом контексте, когда он был написан. Если бы стандарт C писался для описания нового языка, включающего псевдонимы на основе типов, а не для описания существующего языка, построенного на идее о том, что доступ к lvalue — это доступ к битовым шаблонам, хранящимся в памяти, не было бы причин присваивать какой-либо привилегированный статус типу, используемому для хранения символов в строке. Наличие явных операций для обработки областей хранения как битовых шаблонов позволило бы оптимизировать оптимизацию одновременно более эффективно и безопасно. Если бы стандарт C был написан таким образом, стандарт C++, по-видимому, был бы таким же.

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

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

Предположим, что в одном блоке компиляции есть функция:

      void copy_thing(char *dest, char *src, int size)
{
  while(size--)
    *(char volatile *)(dest++) = *(char volatile*)(src++);
}

и в другом блоке компиляции:

      float f1,f2;
float test(void)
{
  f1 = 1.0f;
  f2 = 2.0f;
  copy_thing((char*)&f2, (char*)&f1, sizeof f1);
  return f2;
}

Я думаю, что члены комитета пришли бы к единому мнению, что никакая качественная реализация не должна учитывать тот факт, что copy_thing никогда не записывает в объект типакак приглашение предположить, что возвращаемое значение всегда будет 2.0f. В приведенном выше коде есть много вещей, которые должны препятствовать или препятствовать объединению реализацией чтения с предыдущей записью, со специальным правилом относительно типов символов или без него, но разные реализации могут иметь разные причины для их отказа.

Было бы трудно описать набор правил, которые требовали бы, чтобы все реализации правильно обрабатывали приведенный выше код, не блокируя некоторые существующие или правдоподобные реализации от реализации того, что в противном случае было бы полезной оптимизацией. Реализация, обрабатывающая все межмодульные вызовы как непрозрачные, обработает такой код правильно, даже если она не заметит тот факт, что приведение от T1 к T2 является признаком того, что доступ к T2 может повлиять на T1, или тот факт, что доступ к volatile может повлиять на другие объекты способами, которые компилятор не должен понимать. Реализация, которая выполняла встраивание между модулями и не обращала внимания на последствия приведения типов или volatile, обрабатывала бы такой код правильно, если бы она воздерживалась от каких-либо предположений о псевдонимах при доступе через указатели на символы.

Комитет хотел признать что-то в приведенной выше конструкции, что составители должны будут признать как подразумевающие, чтомогут быть изменены, поскольку альтернативой будет рассмотрение такой конструкции как Undefined Behavior, несмотря на то, что ее можно использовать в переносимых программах. Тот факт, что они выбрали тот факт, что доступ осуществлялся с помощью указателя на символ, был аспектом, который заставил проблему никогда не подразумевать, что компиляторы не обращают внимания на все остальное, хотя, к сожалению, некоторые авторы компиляторов интерпретируют Стандарт как приглашение делать просто что.

Другие вопросы по тегам