Відповідь на питання № 67208

Питання про виділення пам'яті для динамічних масивів.

При черговому виділенні пам'яті під динамічний масив:

type TArrayOfDouble = array of double;
var ArrayRunEvolutionFits: array of TArrayOfDouble;
...
SetLength (ArrayRunEvolutionFits, NRun);

Debugger Eception Notification
Project ProjectPDB.exe raised exception class EAccessViolation with message 'Access violation at address 00405F13 in module' Project.exe '. Read of address 00000003 '. Process stopped. Use Step or Run to continue.

Проект компілюється і програма працює, поки не зустрічається при виконанні вищевказана рядок. Процедури, не пов'язані з цим масивом теж як і раніше працюють.

Помилка стала виникати після чергового додавання нових динамічних масивом (інших), які самі по собі працюють. При їх видаленні помилка перестає виникати.

Програма пишеться в середовищі Delphi 7 і призначена для дослідницьких завдань в галузі вивчення структури молекул білка. У проекті порядку 20 юнітів, використовуються тільки стандартні компоненти і OpenGL для візуалізації молукул. Оскільки заздалегідь розмір масивів даних не відомі, використовуємо динамічні масиви. Самі масиви за обсягом не перевищують 50 000 типу double.

На деякому етапі схожа помилка вже виникала, тоді в Project Options \ Linker збільшення Max stackzize до $ 01000000 дозволило вирішити проблему.

Чи є можливість вирішить цю проблему тепер? Можливо, вкралася помилка виділення пам'яті; може, існують утиліти, що допомагають відслідковувати виділення пам'яті?

Інша можливість, про яку думаю, замінити деякі масиви структурами типу TList. з метою розвантажити stack. Але чи буде це рішенням?

Відстежувати відповіді на це питання по RSS

to Дмитро Шаталов
Причина помилки - банальний вихід за межі масиву який виникає
через некоректне порівняння дійсних чисел.

Якщо Ви поставите лічильники в кожному циклі то побачите, що
- в першому циклі Вам просто «пощастило» - там 4000 ел-тів і 4000 звернень до масиву.
Це тому, що в умови while zd<=40 do begin равенство zd=40 не отрабатывается.
- у другому циклі Вам «не пощастило» - там додається ще 1000 ел-тів і
потім 1001 звернення до масиву. Це тому, що в умови while zd<=50 do begin
рівність zd = 50 відпрацьовується і як наслідок виходить на одне
звернення більше => вихід за межу масиву.

Також якщо є можливість задати кількість ел-тів масиву то краще
це зробити відразу.

Доброго дня. Питання, звичайно, старий, але у мене вознакла така ж невстановлена ​​помилка. Де косяк - незрозуміло. У програмі використовується один динамічний масив:

procedure createpart;
const da_len = 12;
var
profZall: array of array of double;
h: double; // приріст поздовжньої координати
Vr, Vrmin: double; // Контурна і зменшена контурна швидкості різання
j_int: Longword; // інкремент для заповнення масиву
zd, xd: double; // поточні координати деталі

procedure addpoint (z, x, v: double); // заповнення крайнього елемента масиву
var i: longword;
begin
profZall [0, j_int]: = 0; // поки заповнюємо час нульовими значеннями
profZall [1, j_int]: = Z;
profZall [2, j_int]: = X;
profZall [3, j_int]: = V;
for i: = 4 to (da_len- 1) do profZall [i, j_int]: = 0; // нульовими ж значеннями заповнюємо все інше
inc (j_int);
end;

j_int: = 0;
zd: = 0;
// setlength (profZall, da_len, 500000);

// ділянку горизонтальної прямої 40 мм
setlength (profZall, da_len, round (40 / h)); // число ел-в дорівнює 4000
while zd<= 40 do begin
xd: = 50;
addpoint (zd, xd, vr);
zd: = zd + h;
end;

// ділянку чверть окружності - перехід по радіусу
setlength (profZall, da_len, j_int + round (50 / h- 40 / h)); // число ел-в дорівнює 5000
while zd<= 50 do begin
xd: = 60 -roundto (sqrt (sqr (10) -sqr (zd- 40)), - 6);
addpoint (zd, xd, Vrmin);
zd: = zd + h;
end;

// ділянку вісім окружності - перехід по радіусу
setlength (profZall, da_len, j_int- 1 + round (130 / h- 50 / h)); // тут має дорівнювати 13000, але виникає ексепшн

j_int: = j_int; // + debug
end;

Що цікаво, якщо поворожити і додати різний обчислювальний сміття, все може спрацювати і нормально. Ще, завдання свідомо великого числа елементів (50000) проковтує, але ексепшн все одно виникає в тому ж місці.
Ще дуже цікаво поводиться код, якщо процедуру засунути в консольний додаток. Ніби як присвоювання прокатує, але проскакує рядок j_int: = j_int; і вилітає ексепшн InvalidFloatingPointer operation. Присвоєння масиву nil не допомагає. Загалом, глюки якісь.
Так, середовище розробки D7

Результат вирішення проблеми. зовсім несподівано проблема вирішилася (назавжди або лише до пори до часу) перестановкою викликів функцій SetLength:

в вихідному коді виділяється пам'ять для 3 динамічних масивів:
.
SetLength (ArrayRunEvolution, NRun);
SetLength (ArrayRunEvolutionFits, NRun);
SetLength (ArrayRunEvolutionBestFits, NRun);
.
Помилка EAccessViolation виникала у другому рядку для ArrayRunEvolutionFits. В ході вирішення проблеми код був в декількох місцях змінений з огляду на рада 777 по використанню Low (), High (). Але в ході виконання програми помилка як і раніше виникала на одному і тому ж місці.

Випадково, виникла ідея змінити порядок виділення пам'яті. Помилка зникла, коли перші два рядки були переставлені.

Перестановка рядків у вихідне положення до EAccessViolation вже не призводить!

На жаль, тепер не залишилося версії проекту з помилкою. Повернути EAccessViolation тому не вдається. Залишається сподіватися, що помилка була виною Delphi компілятора, а не некоректним використанням динамічних даних.

MBo. зверніть увагу на код:

for iRun: = 1 to nRun do begin
...
ReadRunEvolution (ArrayRunEvolution [iRun-1]);
...
end;

1. тут все правильно.
2. програма не доходить до виконання цього рядка, при виведенні помилки
3. опція "Range checking" включена, і в разі такої помилки б помилка була в ході компіляції
4. раніше це місце працювало (Помилка стала виникати після чергового додавання нових динамічних масивів. При їх видаленні помилка перестає виникати).
5. дуже схоже на згадане Антоном Запариванія пам'яті. Але як це виловить? Намагаюся використовувати Debug для виявлення джерела помилки

> For iRun: = 1 to nRun
Ось тут і собака порилася.
динамічні масиви нумеруються з нуля.

Вважаю, що таких місць в програмі не одне.
Так що рада про Low-High дуже навіть до місця.

Дякую всім за відповіді.

Антон. спасибі за роз'яснення з приводу Запариванія пам'яті. Дуже схоже на це помилку і швидше за все запариваніе не пов'язане з масивом на якому з'являється Access violation. Повторюся, схожа ситуація вже зустрічалася, але джерело помилки, на жаль, не був встановлений. У першому випадку розширили стек і це допомогло, а в другому оптимізували код, видаливши вже непотрібні динамічні структури і програма повністю запрацювала. Тоді складалося враження, що не вистачає стека.

Matvey. спасибі за посилання на acedutils і нагадування про Кнута.

Mbo. ось докладніше код:

type TArrayOfDouble = array of double;
var ArrayRunEvolutionFits: array of TArrayOfDouble;
...
SetLength (ArrayRunEvolutionFits, NRun);
...
for iRun: = 1 to nRun do begin
...
ReadRunEvolution (ArrayRunEvolution [iRun-1]);
...
end;

// де функція ReadRunEvolution діфінірана, як

function ReadRunEvolution (var Fits: TArrayOfDouble);

// і в ній пам'ять виділяється для другого рівня.

Швидше за все помилка виникає раніше, а проявляється лише в цьому рядку. Зауважу ще раз, що якщо програма не використовує цю функцію, виконує інші операції, тоді все працює, помилок не виникає.

Запариваніе пам'яті - це коли в деяку область пам'яті щось пишеться в обхід тих процедур, які її виделелі і несуть за неї відповідальність. В результаті порушується несуперечливість хрангящіхся там даних, що може призводити до самих різних помилок при подальших операціях, які зачіпають цю пам'ять.

Інша можливість, про яку думаю, замінити деякі масиви структурами типу TList, з метою розвантажити stack. Але чи буде це рішенням?
При розмірності в 50000 елементів досить ефективними стають деревовидні структури. Алгоритми, причини та математичні викладки про теоретичному швидкодії наведені у Кнута в 6-му розділі третього тому. Так то для початку можна глянути на acedutils.

ype TArrayOfDouble = array of double;
var ArrayRunEvolutionFits: array of TArrayOfDouble;
...
SetLength (ArrayRunEvolutionFits, NRun);

Тут видно, що переменная- двовимірний масив, а SetLength робиться тільки для першого виміру.
Не забуте чи виділення пам'яті під самі масиви другого рівня?

Схожі статті