Любопытный record
[highlight=delphi]
TMD5Digest = record
case Integer of
0: (A, B, C, D: LongInt);
1: (v: array[0..15] of Byte);
end;[/highlight]
И что-то совсем ее не понял. Может объяснит кто? :)
Оффсет расположения в памяти элемента A и v одинаков. Таким образом, изменяя значения вектора v изменяются значения именованых элементов (и наоборот, меняем именованые элементы - меняется вектор).
И небольшое замечание - я бы к объявлению записи добавил модификатор packed, иначе можно огрести проблемы при изменении параметра выравнивания.
TMD5Digest = record
case ContentType:TContentType of
ctAsLongInt: (A, B, C, D: LongInt);
ctAsByteArray: (v: array[0..15] of Byte);
end;
В этом случае запись можно использовать наподобие переменных типа Variant, однако хранить как простые данные, так и объекты и т.п., при этом экономя память. Доступ к нужным данным при этом можно разграничивать посредством расшифровки значений поля ContentType.
А где тут экономия памяти?
У меня часто возникает вопрос, нужна ли вообще подобная экономия памяти в подобных мелочах? Понятно, что огульно расшвыриваться ею не стоит, но и сходить с ума, оптимизируя затраты неохота. Впрочем это, видимо, ситуативно, ведь может быть явление, когда есть структура, в которой можно сэкономить память, а структур таких используется в рантайме великое множество.
Ну это я так, вслух подумал.
Но если ты это делаешь один раз, то разве есть смысл?
Это только один из частных случаев. Ситуевины - они разные бывают. Сейчас, понятное дело, никто не следит особо за байтами. А раньше - оно ох как актуально было.
Но оп-моему такая конструкция не наглядная и с ней надо быть осторожным.
Экономия памяти и packed в масштабе нескольких байт на запись сегодня даже на нетбуках не актуальна, а вот обычные unpacked record'ы могут основательно повысить скорость, ибо процу проще оперировать над указателями, кратными 32 битам. С другой стороны, на сегодняшних компах и бонус в скорости может быть не больше, чем был бы бонус в памяти от packed record'ов...
Я лично использую packed только для тех типов, которые пишутся в файл.
В этом случае запись можно использовать наподобие переменных типа Variant, однако хранить как простые данные, так и объекты и т.п., при этом экономя память.
Вообще говоря, TVarRec именно так и определён:
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
// и т.д.
Это по-моему один из очень немногих случаев, когда использование case в record весьма оправдано.
Вместо 4*4 байт для 4х Integer + 16 байт для array of Byte = 32, используется всего 16 и под то, и под другое.
Просто MaitreDesir по-моему привёл неудачный пример, TVarRec нагляднее.
Вместо 4*4 байт для 4х Integer + 16 байт для array of Byte = 32, используется всего 16 и под то, и под другое.
Такие конструкции нужны для реинтерпретации памяти, например, для отображения массива на набор именованных элементов. А для экономии памяти нужно алгоритмы правильные выбирать, а не структуры данных нарушающие принцип инкапсуляции - это я имел в виду а теперь выразил словами.
Это тоже верно, тем более, что экономить память на таких вещах сродни предоптимизации.
В советское время это называли привидением типа.
Это не приведение типа, но реинтерпретация памяти, так как мы не производим действий ни для проверки возможности преобразования объекта одного типа в другой, ни действий для этого.
[...]
{ Type conversion records }
WordRec = packed record
case Integer of
0: (Lo, Hi: Byte);
1: (Bytes: array [0..1] of Byte);
end;
LongRec = packed record
[...]
Int64Rec = packed record
Взято из SysUtils.pas, в тему ТС.
[...]
{ Type conversion records }
WordRec = packed record
case Integer of
0: (Lo, Hi: Byte);
1: (Bytes: array [0..1] of Byte);
end;
LongRec = packed record
[...]
Int64Rec = packed record
Взято из SysUtils.pas, в тему ТС.
Будет ли следующее объявление служить приведением типа между Integer и Real?
case Integer of
0: (X, Y : Integer);
1: (Z : real);
end;
А также TButton и string?
case Integer of
0: (X : TButton);
1: (Z : string);
end;
З.Ы. я рассуждаю с позиций развитых систем типов C++ и .NET
А что требуется получить? Если нужно показать низкоуровневое представление вещественных типов сопроцессором - очень даже, только в 16-ричном виде вывести.
А TButton (TObject) и string часто приводят к Integer и наоборот. Тема ж не про .NET. В советское время .NET-а не было. :D
А TButton (TObject) и string часто приводят к Integer и наоборот. Тема ж не про .NET. В советское время .NET-а не было. :D
Проблема в том, что Delphi не различает операцию "приведения типа", которая вообще-то является функцией конструирующей объект целевого типа эквивалентный объекту приводимого типа, и операцию "переинтерпретации памяти" когда мы компилятору говорим что по адресу известной переменной находится объект совершенно иного типа. Первая операция является типобезопасной, вторая же - нет.
И да, причем тут советское время?
case Integer of
0: (X, Y : Integer);
1: (Z : real);
end;
В Delphi 7 Real является псевдонимом типа Double:
Вместо него лучше пользоваться 10 байтным типом Extended - к которому по умолчанию приводятся значения вещественных выражений.
Что касается форматов представления целых и вещественных чисел в памяти - они совершенно различные.
Целые представляются таким образом, что младшие байты числа расположены в ячейках с младшими адресами, а старшие - в старших, соответственно. Отрицательные целые числа представляются в дополнительном коде.
Вещественные - представлены в памяти в направлении от старших адресов - к младшим: один знаковый разряд, группа разрядов экспоненты, группа разрядов нормализованной мантиссы (в десятичном виде значение равно от 0 включительно до 2 с исключением).
Представление вещественных чисел можно поизучать на онлайн калькуляторах IEEE.
А также TButton и string?
case Integer of
0: (X : TButton);
1: (Z : string);
end;
Вместо этого кода удобнее написать так:
//и:
Button1 := TButton( Pointer(Str1) );
Смысла в этом нет, если в проге не придумано что-то такое неизвестно зачем замудрёное. :)
Строка тип String - это указатель на ячейку (байт) в памяти, начиная с которого расположен массив символов строки. Последним символом является терминальный ноль #0 - распложен в позиции:
Str1[Length(Str1) + 1)
Кроме этого строка содержит метаданные - это сведения о размере выделенной памяти, количестве ссылок на строку, длина строки (без учёта терминального нуля). Эти метаданные расположены в младших адресах перед массивом символов строки:
P, PSize, PRefCnt, PLen : ^Integer;
i : Integer;
Str1 : String;
begin
SetLength(Str1, 20);
for i := 1 to Length(Str1) do Str1 := '1';
P := Pointer(Str1);
PSize := Pointer( Integer(P) - 12 );
PRefCnt := Pointer( Integer(P) - 8 );
PLen := Pointer( Integer(P) - 4 );
ShowMessage(
'Строка: "' + Str1 + '"' + #10
+ 'Общий объём памяти (байт), выделенной для строки = ' + IntToStr(PSize^) + #10
+ 'Длина (количество символов) без учёта терм. нуля = ' + IntToStr(PLen^) + #10
+ 'Количество ссылок на строку = ' + IntToStr(PRefCnt^)
);
end;
В Delphi 7 объём памяти для строки равен:
Str1 : String;
MemSize, AnyAdd : Integer;
begin
...
MemSize := 3 * SizeOf(Integer) + Length(Str1) * SizeOf(Char) + 1 * SizeOf(Char) + AnyAdd;
AnyAdd - это некоторая величина, выбираемая менеджером памяти Delphi. Эти дополнительные байты видимо расположены в старших адресах относительно терминального нуля. Я не встречал в инете какой-либо инфы об их назначении. Чем длиннее строка, тем значение AnyAdd может быть большим.
Что касается Button1 : TButton и вообще любого экземпляра класса (объекта) - это указатель на ячейку памяти, начиная с которой расположены сами данные экземпляра (объекта).
Я рад что вы хорошо разбираетесь в Delphi.
Правда вопросы мои были не от того, что я не знаю предмета, а для того, чтобы показать разницу в интерпретации понятия "приведение типа". С позиций одних языков - это переинтерпретация памяти, с позиций же других языков - это честное типобезопасное конвертирование значений.
...
:D
вообще вопрос был задан какбэ риторический, и если уж требовал на него ответа то достаточно было "да" или "нет".
ПыСы. Харкейс, опередил! :)
Правда вопросы мои были не от того, что я не знаю предмета, а для того, чтобы показать разницу в интерпретации понятия "приведение типа". ...
Сейчас перечитал посты - да я в контекст не вник - прямолинейно по вопросам ответил. :D
вообще вопрос был задан какбэ риторический, и если уж требовал на него ответа то достаточно было "да" или "нет".
ПыСы. Харкейс, опередил! :)
Понятно. :)