Строгое правило алиасинга и указатели 'char *'
Принятый ответ на вопрос " Что такое строгое правило псевдонимов"? упоминает, что вы можете использовать char *
псевдоним другого типа, но не другой путь.
Это не имеет смысла для меня - если у нас есть два указателя, один из типа char *
и другой тип struct something *
указывая на то же место, как это возможно, что первый псевдоним второго, а второй не псевдоним первого?
4 ответа
если у нас есть два указателя, один из типа
char *
и другой типstruct something *
указывая на то же место, как это возможно, что первый псевдоним второго, а второй не псевдоним первого?
Да, но это не главное.
Дело в том, что если у вас есть один или несколько struct something
с, то вы можете использовать char*
читать их составляющие байты, но если у вас есть один или несколько char
с, то вы не можете использовать struct something*
читать их.
Формулировка в ссылочном ответе немного ошибочна, поэтому давайте сначала проясним это: один объект никогда не псевдоним другого объекта, но два указателя могут "псевдоним" одного и того же объекта (то есть указатели указывают на одну и ту же область памяти - как указывал MM это все еще не на 100% правильная формулировка, но вы получите идею). Кроме того, сам стандарт (насколько мне известно) вообще не говорит о строгом псевдониме, а только дает правила, с помощью которых можно получить доступ к типу выражений к объекту или нет. Флаги компилятора, такие как -fno-strict-aliasing, сообщают компилятору, может ли он предположить, что программист следует этим правилам (поэтому он может выполнять оптимизацию на основе этого предположения) или нет.
Теперь к вашему вопросу: любой объект может быть доступен через указатель на char
, но char
к объекту (особенно к массиву символов) нельзя получить доступ через большинство других типов указателей. Исходя из этого, компилятор может / должен сделать следующие предположения:
- Если тип самого объекта неизвестен,
char*
а такжеT*
всегда может указывать на один и тот же объект (псевдоним друг друга) -> симметричные отношения. - Если
T1
а такжеT2
не "связаны" и неchar
, затемT1*
а такжеT2*
может никогда не указывать на один и тот же объект -> симметричные отношения -
char*
может указывать наchar
ИЛИT
объект -
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 никогда не записывает в объект типа
Было бы трудно описать набор правил, которые требовали бы, чтобы все реализации правильно обрабатывали приведенный выше код, не блокируя некоторые существующие или правдоподобные реализации от реализации того, что в противном случае было бы полезной оптимизацией. Реализация, обрабатывающая все межмодульные вызовы как непрозрачные, обработает такой код правильно, даже если она не заметит тот факт, что приведение от T1 к T2 является признаком того, что доступ к T2 может повлиять на T1, или тот факт, что доступ к volatile может повлиять на другие объекты способами, которые компилятор не должен понимать. Реализация, которая выполняла встраивание между модулями и не обращала внимания на последствия приведения типов или volatile, обрабатывала бы такой код правильно, если бы она воздерживалась от каких-либо предположений о псевдонимах при доступе через указатели на символы.
Комитет хотел признать что-то в приведенной выше конструкции, что составители должны будут признать как подразумевающие, что