Задан тип структуры
Type TRec = record
f1: Type1;
.........
fn: TypeN;
end;
статическая переменная и динамический массив с этой структурой
var
vr: TRec;
DA : array of TRec;
Так вот, формат структуры после компилирования (расположение и размер полей) будут разные у переменной и динамического массива.
В динамическом массиве компилятор оптимизирует расположение полей, а в переменной - нет.
Но это полностью нарушает работу программы, т.к. в ней передаются адреса (ссылки) на переменные с этим типом и нарушение полей структуры всё портит.
Вопрос, есть ли какая-нибудь директива компилятора, которая пресекает подобную его самодеятельность? (т.е., чтобы размер полей и их расположение не менялись).
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
21-10-2020 03:07
В type перед ключевым словом record поставьте packed, тогда переменные в типе будут размещаться как они есть без выравнивания:
procedure PrintInternal(const V;Size:word);
var
P:PByte;
begin
P:=@V;
while Size>0 do begin
Write(inttohex(P^,2),' ');
Inc(P);
Dec(Size);
end;
Writeln;
end;
var
P:TPony;
Ps:TPoniez;
I:integer;
begin
P.Twilight:=$AA;
P.Rarity:=$1234;
P.Applejack:='Applez';
SetLength(Ps,4);
For I:=0 to 3 do begin
Ps[I].Twilight:=I;
Ps[I].Rarity:=2*I;
Ps[I].Applejack:=inttostr(I*4);
end;
PrintPony(@P);
PrintInternal(P,sizeof(P));
For I:=0 to 3 do begin
PrintPony(@Ps[I]);
PrintInternal(Ps[I],sizeof(Ps[I]));
end;
Readln;
end.
Результат для по умолчанию:
Twilight=AA Rarity=1234 Applejack=00416034
AA 00 34 12 34 60 41 00
Twilight=00 Rarity=0000 Applejack=0006079C
00 00 00 00 9C 07 06 00
Twilight=01 Rarity=0002 Applejack=000607BC
01 00 02 00 BC 07 06 00
Twilight=02 Rarity=0004 Applejack=000607DC
02 00 04 00 DC 07 06 00
Twilight=03 Rarity=0006 Applejack=000607FC
03 00 06 00 FC 07 06 00
Результат с директивой $A- (нет выравнивания):
Twilight=AA Rarity=1234 Applejack=00416034
AA 34 12 34 60 41 00
Twilight=00 Rarity=0000 Applejack=0006079C
00 00 00 9C 07 06 00
Twilight=01 Rarity=0002 Applejack=000607BC
01 02 00 BC 07 06 00
Twilight=02 Rarity=0004 Applejack=000607DC
02 04 00 DC 07 06 00
Twilight=03 Rarity=0006 Applejack=000607FC
03 06 00 FC 07 06 00
Вывод: структура записи в памяти не зависит от того, находится ли она в массиве, или просто валяется где-то как отдельная переменная, тогда как даже внутри одного коноплятора, но при разных настройках оного, выравнивание полей может меняться, чего уж говорить о случае, где используются разные конопляторы. Впрочем, данный вывод естественным образом вытекает из документации и никакой ценности из себя не представляет.
09-10-2020 08:11 | Комментарий к предыдущим ответам
Я тоже погонял тестовую программу (правда, на D7). И тоже для SizeOf получил либо 20, либо 24. Но когда заглянул внутрь (дамп памяти посмотрел), такое ощущение, что даже при выравнивании на 4 байта, поля в записи идут плотно. Дальше рыть не стал, так как floating-point типы в памяти читаю плохо, а детально разбираться лениво.
Есть хорошее правило. Если работаешь с record честно (предусмотренными штатным возможностями языка), то глубоко плевать, как там в памяти это хранится: компилятор знает сам и сделает правильно. Если же предполагается низкоуровневая работа (использование record как буфер с некоторой структурой), то используй packed record со строго определенным и документированным расположением полей.
Выравнивание полей простых типов в записях (и объектах) по-умолчанию подчиняется правилу:
типы размером 1 байт (byte, char) - на границу байта
типы размером 2 байта (word, widechar) - на границу слова (адрес кратен 2)
типы размером 4 байта (integer, float) - на границу двойного слова (адрес кратен 4)
типы размером 8 байт (int64, double) - на границу 64 бит (адрес кратен 8)
тип extended - на границу 128 бит (адрес кратен 16)
Выравнивание можно отменить, если вместо record написать packed record, или ввести директиву {$A-} или {$A1}.
Тогда все поля будут выравниваться на границу байта, т.е. упакованы плотно.
Директива с явным указанием границы задаёт максимальную границу.
Любые переменные и массивы от одного объявленного типа записи будут иметь идентичную структуру в памяти.
Поэтому я спорю с этим вашим утверждением:
Так вот, формат структуры после компилирования (расположение и размер полей) будут разные у переменной и динамического массива.
В динамическом массиве компилятор оптимизирует расположение полей, а в переменной - нет.
Вот тестовый пример из элементов вашего кода.
program AlignTest;
{$APPTYPE CONSOLE}
type
TComplex = record
re: Extended;
im: Extended;
end;
var z: TComplex;
DCX: array of TComplex;
begin
Writeln(SizeOf(z):4, SizeOf(z.re):4, SizeOf(z.im):4);
Writeln(SizeOf(dcx[0]):4, SizeOf(dcx[0].re):4, SizeOf(dcx[0].im):4);
Writeln(Integer(@dcx[0]):4,Integer(@dcx[1]):4);
end.
Опция {$A16} - то же, что и {$A8} (для данного случая).
Поэтому, уважаемый автор, ошибка у вас не в компиляторе, а совсем в другом месте.
Не нужно делать предположения о размере структур и элементов массива. Нужно использоваать SizeOf()
ЗЫ. В Си для достижения плотной упаковки используют директиву #pragma pack(1). А по-умолчанию - такие же правила как и в Паскале.
Есть такая вещь, как выравнивание адресов данных на адреса кратные... э-э-э,,, как бы это сказать... кратные количеству байт в разрядности процессора :-)
Для 16-разрядных системы выравнивание производилось по четным адресам, для 32-разрядных — на адреса, кратные 4. Предполагаю, что для 64-разрядных систем выравнивание выполняется на адреса, кратные 8. В этом случае доступ к данным происходит чуть быстрее.
Если вы не хотите такой оптимизации, используйте packed record. В этом случае выравнивание для оптмизиции выполняться не будет, а поля буду идти подряд плотно.
:) Значит вы не сталкивались с этой проблемой. Многие языки этим грешат. Я на форумах встречал такие обсуждения. К тому же в разных языках одна и та же структура может компилироваться по разному.
Простой пример в Дельфи
Type TComplex = record
re: Extended;
im: Extended;
end;
при статическом объявлении
z: TComplex;
размеры полей re,im будут 12 байтов. (в GCC эти поля имеют размер уже 16 байтов)
Но при задании динамического массива от этой структуры
DCX: array of TComplex;
размеры каждого поля становятся 10 байт. (как и должно быть в типе Extended).
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.