Тип каламбура

редактировать

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

В C и C ++ такие конструкции, как преобразование типа указателя и - C ++ добавляет преобразование ссылочного типа и в этот список - предусмотрены для того, чтобы разрешить многие виды каламбура типов, хотя некоторые виды фактически не поддерживаются стандартным языком. union reinterpret_cast

В языке программирования Pascal использование записей с вариантами может использоваться для обработки определенного типа данных более чем одним способом или способом, который обычно не разрешен.

Содержание
  • 1 Пример сокетов
  • 2 Пример с плавающей точкой
  • 3 По языку
    • 3.1 C и C ++
      • 3.1.1 Использование указателей
      • 3.1.2 Использование объединения
    • 3.2 Паскаль
    • 3.3 C #
      • 3.3.1 Указатели
      • 3.3.2 Структурные союзы
      • 3.3.3 Исходный код CIL
  • 4 ссылки
  • 5 Внешние ссылки
Пример сокетов

Один классический пример каламбура можно найти в интерфейсе сокетов Беркли. Функция привязки открытого, но неинициализированного сокета к IP-адресу объявляется следующим образом:

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

bind Функция обычно называется следующим образом:

struct sockaddr_in sa = {0}; int sockfd =...; sa.sin_family = AF_INET; sa.sin_port = htons(port); bind(sockfd, (struct sockaddr *)amp;sa, sizeof sa);

Библиотека сокетов Беркли в основном основана на том факте, что в C указатель на struct sockaddr_in свободно конвертируется в указатель на struct sockaddr ; и, кроме того, эти два типа структур используют одну и ту же схему памяти. Следовательно, ссылка на поле структуры my_addr-gt;sin_family (где my_addr имеет тип struct sockaddr*) фактически будет относиться к полю sa.sin_family (где sa имеет тип struct sockaddr_in). Другими словами, библиотека сокетов использует каламбур для реализации элементарной формы полиморфизма или наследования.

В мире программирования часто встречается использование «дополненных» структур данных, позволяющих хранить различные типы значений в том же самом пространстве хранения. Это часто наблюдается, когда две структуры используются во взаимной исключительности для оптимизации.

Пример с плавающей точкой

Не во всех примерах каламбура используются структуры, как в предыдущем примере. Предположим, мы хотим определить, является ли число с плавающей запятой отрицательным. Мы могли бы написать:

bool is_negative(float x) { return x lt; 0.0; }

Однако, предположив, что сравнения с плавающей запятой являются дорогостоящими, а также предположим, что float это представлено в соответствии со стандартом с плавающей запятой IEEE, а целые числа имеют ширину 32 бита, мы могли бы использовать каламбур, чтобы извлечь знаковый бит числа с плавающей запятой используя только целочисленные операции:

bool is_negative(float x) { unsigned int *ui = (unsigned int *)amp;x; return *ui amp; 0x80000000; }

Обратите внимание, что поведение не будет точно таким же: в частном случае x являются отрицательным нуль, первыми выходами реализации в false то время как второй урожайность true. Кроме того, первая реализация вернет false любое значение NaN, но последнее может вернуть true значения NaN с установленным битом знака.

Этот вид каламбура более опасен, чем большинство других. В то время как первый пример основывается только на гарантиях, сделанных языком программирования C относительно компоновки структуры и конвертируемости указателей, последний пример основывается на предположениях об аппаратном обеспечении конкретной системы. В некоторых ситуациях, таких как критичный по времени код, который компилятор не может оптимизировать в противном случае, может потребоваться опасный код. В этих случаях документирование всех таких предположений в комментариях и введение статических утверждений для проверки ожиданий переносимости помогает сохранить поддерживаемый код.

Практические примеры каламбура с плавающей запятой включают быстрый обратный квадратный корень, популярный в Quake III, быстрое сравнение FP как целые числа и поиск соседних значений путем увеличения как целого числа (реализация nextafter).

По языку

C и C ++

В дополнение к предположению о битовом представлении чисел с плавающей запятой, приведенный выше пример каламбура типа с плавающей запятой также нарушает ограничения языка C на доступ к объектам: объявленный тип x is, float но он считывается через выражение типа unsigned int. На многих распространенных платформах такое использование перфорации указателей может создать проблемы, если разные указатели выровнены машинно-зависимыми способами. Более того, указатели разных размеров могут использовать псевдонимы доступа к одной и той же памяти, вызывая проблемы, которые компилятор не контролирует.

Использование указателей

Наивная попытка набора текста может быть достигнута с помощью указателей:

float pi = 3.14159; uint32_t piAsRawData = *(uint32_t*)amp;pi;

Согласно стандарту C этот код не должен (или, скорее, не обязательно) компилироваться, однако, если он это делает, то piAsRawData обычно содержит необработанные биты числа пи.

Использование union

Распространенной ошибкой является попытка исправить каламбур с помощью расширения union. (В следующем примере дополнительно делается предположение о битовом представлении IEEE-754 для типов с плавающей запятой).

bool is_negative(float x) { union { unsigned int ui; float d; } my_union = {.d = x }; return my_union.ui amp; 0x80000000; }

Доступ my_union.ui после инициализации другого члена, my_union.d по-прежнему является формой каламбура в C, и результатом является неопределенное поведение неопределенное поведение в C ++).

Язык § 6.5 / 7 может быть неправильно истолкован, чтобы подразумевать, что чтение альтернативных членов союза допустимо. Однако текст гласит: «Сохраненное значение объекта должно быть доступно только …». Это ограничивающее выражение, а не утверждение, что все возможные члены объединения могут быть доступны независимо от того, какой из них был сохранен последним. Таким образом, использование не union позволяет избежать проблем, связанных с прямым перенаправлением указателя.

Его можно даже считать менее безопасным, чем перенаправление типов с использованием указателей, поскольку компилятор с меньшей вероятностью сообщит о предупреждении или ошибке, если он не поддерживает перенаправление типов.

Компиляторы, такие как GCC, поддерживают доступ к псевдонимам, как в приведенных выше примерах, в качестве языкового расширения. На компиляторах без такого расширения правило строгого псевдонима нарушается только явным memcpy или использованием указателя char в качестве «посредника» (поскольку они могут иметь произвольный псевдоним).

Другой пример каламбура см. В разделе Шаг массива.

Паскаль

Вариантная запись позволяет обрабатывать тип данных как несколько видов данных в зависимости от того, на какой вариант делается ссылка. В следующем примере предполагается, что целое число является 16-битным, в то время как longint и real предполагается равным 32, а символ - 8-битным:

type VariantRecord = record case RecType: LongInt of 1: (I: array[1..2] of Integer); (* not show here: there can be several variables in a variant record's case statement *) 2: (L: LongInt); 3: (R: Real); 4: (C: array[1..4] of Char); end; var V: VariantRecord; K: Integer; LA: LongInt; RA: Real; Ch: Character; V.I[1]:= 1; Ch:= V.C[1]; (* this would extract the first byte of V.I *) V.R:= 8.3; LA:= V.L; (* this would store a Real into an Integer *)

В Паскале копирование действительного числа в целое число преобразует его в усеченное значение. Этот метод преобразует двоичное значение числа с плавающей запятой в какое-либо длинное целое число (32 бита), которое не будет таким же и может быть несовместимо со значением длинного целого числа в некоторых системах.

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

type PA = ^Arec; Arec = record case RT: LongInt of 1: (P: PA); 2: (L: LongInt); end; var PP: PA; K: LongInt; New(PP); PP^.P:= PP; WriteLn('Variable PP is located at address ', Hex(PP^.L));

Где «новый» - это стандартная процедура в Паскале для выделения памяти для указателя, а «шестнадцатеричный» - предположительно процедура для печати шестнадцатеричной строки, описывающей значение целого числа. Это позволит отображать адрес указателя, что обычно не разрешается. (Указатели не могут быть прочитаны или записаны, их можно только присвоить.) Присвоение значения целочисленному варианту указателя позволит исследовать или записывать в любое место в системной памяти:

PP^.L:= 0; PP:= PP^.P; (* PP now points to address 0 *) K:= PP^.L; (* K contains the value of word 0 *) WriteLn('Word 0 of this machine contains ', K);

Эта конструкция может вызвать проверку программы или нарушение защиты, если адрес 0 защищен от чтения на машине, на которой выполняется программа, или в операционной системе, в которой она работает.

Техника переинтерпретирования приведения из C / C ++ также работает в Паскале. Это может быть полезно, например, когда. чтение двойных слов из байтового потока, и мы хотим рассматривать их как плавающие. Вот рабочий пример, в котором мы переинтерпретируем преобразование двойного слова в поплавок:

type pReal = ^Real; var DW: DWord; F: Real; F:= pReal(@DW)^;

C #

В C # (и других языках.NET) каламбура типов добиться немного сложнее из-за системы типов, но, тем не менее, это можно сделать с помощью указателей или объединений структур.

Указатели

C # разрешает указатели только на так называемые собственные типы, то есть любой примитивный тип (кроме string), перечисление, массив или структуру, которые состоят только из других собственных типов. Обратите внимание, что указатели разрешены только в блоках кода, помеченных как «небезопасные».

float pi = 3.14159; uint piAsRawData = *(uint*)amp;pi;

Структурные союзы

Союзы структур разрешены без какого-либо понятия «небезопасный» код, но они требуют определения нового типа.

[StructLayout(LayoutKind.Explicit)] struct FloatAndUIntUnion { [FieldOffset(0)] public float DataAsFloat; [FieldOffset(0)] public uint DataAsUInt; } //... FloatAndUIntUnion union; union.DataAsFloat = 3.14159; uint piAsRawData = union.DataAsUInt;

Исходный код CIL

Raw CIL можно использовать вместо C #, потому что он не имеет большинства ограничений типа. Это позволяет, например, объединить два значения перечисления универсального типа:

TEnum a =...; TEnum b =...; TEnum combined = a | b; // illegal

Это можно обойти с помощью следующего кода CIL:

.method public static hidebysig !!TEnum CombineEnumslt;valuetype.ctor ([mscorlib]System.ValueType) TEnumgt;( !!TEnum a, !!TEnum b) cil managed {.maxstack 2 ldarg.0 ldarg.1 or // this will not cause an overflow, because a and b have the same type, and therefore the same size. ret }

Код cpblk операции CIL позволяет использовать некоторые другие приемы, такие как преобразование структуры в массив байтов:

.method public static hidebysig uint8[] ToByteArraylt;valuetype.ctor ([mscorlib]System.ValueType) Tgt;( !!Tamp; v // 'ref T' in C#) cil managed {.locals init ( [0] uint8[]).maxstack 3 // create a new byte array with length sizeof(T) and store it in local 0 sizeof !!T newarr uint8 dup // keep a copy on the stack for later (1) stloc.0 ldc.i4.0 ldelema uint8 // memcpy(local 0, amp;v, sizeof(T)); // lt;the array is still on the stack, see (1)gt; ldarg.0 // this is the *address* of 'v', because its type is '!!Tamp;' sizeof !!T cpblk ldloc.0 ret }
использованная литература
  1. ^ Херф, Майкл (декабрь 2001 г.). "хитрости счисления". стереопсис: графика.
  2. ^ «Глупые трюки с плавающей запятой». Случайный ASCII - технический блог Брюса Доусона. 24 января 2012 г.
  3. ^ a b ISO / IEC 9899: 1999 s6.5 / 7
  4. ^ «§ 6.5.2.3/3, сноска 97», ISO / IEC 9899: 2018 (PDF), 2018, стр. 59, заархивировано из исходного (PDF) 30 декабря 2018 г. Если член, используемый для чтения содержимого объекта union, не совпадает с членом, последним использовавшимся для хранения значения в объекте, соответствующая часть объектное представление значения переинтерпретируется как объектное представление в новом типе, как описано в 6.2.6 ( процесс, иногда называемый «каламбуром типов» ). Это может быть ловушка.
  5. ^ «§ J.1 / 1, bullet 11», ISO / IEC 9899: 2018 (PDF), 2018, стр. 403, заархивировано из оригинала (PDF) 30 декабря 2018 г. Следующее не определено:… Значения байтов, которые соответствуют членам объединения,кроме последнего, сохраненного в (6.2.6.1).
  6. ^ ISO / IEC 14882: 2011 Раздел 9.5
  7. ^ GCC: не содержит ошибок
внешние ссылки
  • Раздел руководства GCC по -fstrict-aliasing, который побеждает некоторые типы каламбура
  • Отчет о дефектах 257 в соответствии со стандартом C99, случайно определяющий «каламбур типов» в терминах union и обсуждение вопросов, связанных с поведением, определяемым реализацией, в последнем примере выше.
  • Отчет о дефектах 283 об использовании объединений для набора текста
Последняя правка сделана 2024-01-06 10:52:10
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте