Правильно ли я понимаю строгое сглаживание C/С++?

Я прочитал эту статью о строгом псевдониме C/С++. Я думаю, что то же самое относится к С++.

Как я понимаю, строгое псевдонижение используется для изменения кода для оптимизации производительности. Поэтому два указателя разных (и не связанных между собой типов С++) типов не могут ссылаться на одно и то же место в памяти.

Означает ли это, что проблемы могут возникать только при изменении памяти? Помимо возможных проблем с выравниванием памяти.

Например, обработка сетевого протокола или де-сериализация. У меня есть массив байтов, динамически распределенный и пакетная структура правильно выровнены. Могу ли я reinterpret_cast его передать в мою структуру пакетов?

char const* buf = ...; // dynamically allocated
unsigned int i = *reinterpret_cast<unsigned int*>(buf + shift); // [shift] satisfies alignment requirements

Ответ 1

Проблема здесь не в строгом псевдониме, а в требованиях представления структуры.

Во-первых, это безопасно для псевдонимов между char, signed char или unsigned char и любым другим типом (в вашем случае unsigned int). Это позволяет вам писать свои собственные циклы копирования памяти, так как поскольку они определены с использованием типа char. Это разрешено в C99 (§6.5) следующим языком:

  6. Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта, если таковой имеется. [Сноска: Выделенные объекты не имеют объявленного типа] [...] Если значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип измененного объекта для этого доступа и для последующих доступов, которые не изменяют value - это эффективный тип объекта, из которого копируется значение, если оно есть. Для все другие обращения к объекту, не имеющему объявленного типа, эффективный тип объекта просто тип lvalue, используемый для доступа.

  7. Объект должен иметь сохраненное значение, доступ к которому имеет только выражение lvalue, которое имеет один из следующих типов: [Сноска: Цель этого списка состоит в том, чтобы указать те обстоятельства, в которых объект может быть или не быть псевдонимом.]

  • тип, совместимый с эффективным типом объекта,
  • [...]
  • тип символа.

Сходный язык можно найти в черновик С++ 0x N3242 §3.11/10, хотя это не так ясно, когда назначается "динамический тип" объекта (я буду благодарен за любые дальнейшие ссылки на то, что динамическое type имеет массив char, к которому объект POD был скопирован как массив char с правильным выравниванием).

Таким образом, сглаживание здесь не является проблемой. Тем не менее, строгое чтение стандарта указывает на то, что реализация С++ имеет большую свободу в выборе представления unsigned int.

В качестве одного случайного примера unsigned int может быть 24-разрядным целым числом, представленным в четырех байтах, с 8 переполненными битами заполнения; если какой-либо из этих битов дополнений не соответствует определенному (постоянному) шаблону, он рассматривается как ловушечное представление, и разыменование указателя приведет к сбою. Является ли это вероятной реализацией? Возможно нет. Но были исторически системы с битами четности и другой странностью, и поэтому прямое считывание из сети в unsigned int путем строгого чтения стандарта не является кошерным.

Теперь проблема заполнения битов в большинстве случаев является теоретической проблемой для большинства систем, но стоит отметить. Если вы планируете придерживаться аппаратного обеспечения ПК, вам не нужно беспокоиться об этом (но не забывайте, что ваш ntohl - endianness все еще проблема!)

Структуры делают это еще хуже, конечно - представления выравнивания зависят от вашей платформы. Я работал над встроенной платформой, в которой все типы имеют выравнивание 1 - никакие дополнения никогда не вставляются в структуры. Это может привести к несогласованности при использовании тех же определений структуры на нескольких платформах. Вы можете вручную выработать смещения байтов для элементов структуры данных и напрямую ссылаться на них или использовать директиву для определения соответствия для компилятора для контроля заполнения.

Таким образом, вы должны быть осторожны при прямом литье из сетевого буфера в собственные типы или структуры. Но само это наложение не является проблемой.

Ответ 2

На самом деле этот код уже имеет UB в точке, где вы разыскиваете указатель целых чисел reinterpret_cast ed, даже не требуя применения правил строгого сглаживания. Не только это, но если вы не очень осторожны, переинтерпретация непосредственно в вашу структуру пакетов может вызвать всевозможные проблемы в зависимости от структуры и конкретизации структуры.

Учитывая все это, и что вы уже вызываете UB, я подозреваю, что он "может работать" на нескольких компиляторах, и вы можете принять этот (возможно измеримый) риск.