(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Цикл for.Выполняется итерация для пустого списка

Источник: delphikingdom
Алексей Михайличенко

При работе со списками вроде TList, и вообще везде, где есть свойство Count, обычным является перебор элементов в цикле for:

 list := TList.Create;

 for i := 0 to list.Count-1 do
    обращение к list.Items[i]...

Если переменная i имеет тип Integer, то все работает замечательно.

Но если переменная i имеет тип Word (напомним, этот тип допускает диапазон 0..65535, что, казалось бы, достаточно для большинства списков), то проект компилируется без проблем, но в работе происходит ошибка:

На пустом списке происходит вход в тело цикла, обращение к Items[0], и, соответственно, ошибка List index out of bounds (0).

Отключение оптимизации на ситуацию не влияет.

Понятно, что для пустого списка (Count = 0) цикл выполняться вообще не должен: for i := 0 to -1 - нет проходов.

Причина ошибки в том, что при вхождении в цикл выполняется сравнение переменной цикла типа Word (которая, как мы уже сказали, понимает только 0..65535) с числом со знаком (-1), и происходит ошибка переполнения типа Word.

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

var i: Word;
for i := 0 to -1 do

[Error] Unit1.pas(34): Constant expression violates subrange bounds

Психологическая подоплека ошибки - в том, что при представлении заполненного списка программист совершенно справедливо подразумевает неотрицательное множество индексов - 0,1,2 и далее, и ему так и просится беззнаковая переменная цикла.

Технические подробности ошибки: код для воспроизведения, и соответствующий компилированный код:

   procedure TForm1.Button1Click(Sender: TObject);
   var
     list: TList;
     i: word;
   begin
     list := TList.Create;

34    for i := 0 to list.Count-1 do
35      if assigned(list.Items[i]) then // произвольное обращение к элементу
36        ShowMessage('assigned');
37
38    list.Free;
   end;
Unit1.pas.34: for i := 0 to list.Count-1 do

00452985 668B5F08         mov bx,[edi+$08]
00452989 4B               dec ebx
0045298A 6685DB           test bx,bx           <<<<<<<<<<<<<<<<<<<
ошибка сравнения здесь
0045298D 7221             jb +$21              <<<<<<<<<<<<<<<<<<<
перехода на конец цикла не происходит
0045298F 43               inc ebx
00452990 33F6             xor esi,esi

Unit1.pas.35: if assigned(list.Items[i]) then

00452992 0FB7D6           movzx edx,si
00452995 8BC7             mov eax,edi
00452997 E8DC15FCFF       call TList.Get
0045299C 85C0             test eax,eax
0045299E 740A             jz +$0a

Unit1.pas.36: ShowMessage('assigned');

004529A0 B8C4294500       mov eax,$004529c4
004529A5 E87E50FDFF       call ShowMessage
004529AA 46               inc esi

Unit1.pas.34: for i := 0 to list.Count-1 do

004529AB 66FFCB           dec bx
004529AE 75E2             jnz -$1e

Unit1.pas.38: list.Free;

А вот работающий код, для переменной типа Integer:

Unit1.pas.34: for i := 0 to list.Count-1 do

00452985 8B5F08           mov ebx,[edi+$08]
00452988 4B               dec ebx
00452989 85DB             test ebx,ebx
0045298B 7C1E             jl +$1e             <<<<<<<<<<<<<<<<<<<<<<<

переход происходит
0045298D 43               inc ebx
0045298E 33F6             xor esi,esi


004529A8 4B               dec ebx
004529A9 75E5             jnz -$1b




Типовые решения


Следует использовать для переборов списков только Integer-переменные цикла. А уж если важна оптимизация нескольких байт - то более короткие, но обязательно знаковые типы: Shortint -128..127, Smallint -32768..32767. Либо можно использовать границы цикла for i := 1 to Count, а обращаться к элементу Item[i-1], хотя это будет отличаться от общепринятого подхода, и может вызвать трудности у коллег (все привыкли, что текущий элемент в цикле - это i).

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

Комментарий


Это естественное поведение компилятора, и оно характерно для всех версий Паскаля. Программист не предусмотрел возможного переполнения и получил неожиданный эффект. Обойти ошибку, оставаясь с беззнаковой переменной цикла, можно, если заранее проверять список на пустоту. Но это некрасивое решение. Наиболее грамотным решением будет использовать знаковые целые типы для переменной цикла. И не имеет смысла экономить на размере переменной цикла, даже если для итераций хватит двух или одного байта. Базовым размером для процессора является 4 байта, и с ним он работает наиболее эффективно, а компилятор генерит наиболее эффективный код.

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

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 03.08.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
Delphi Professional Named User
VMware Workstation Pro 12 for Linux and Windows, ESD
NauDoc Enterprise 10 рабочих мест
IBM Domino Messaging Server Processor Value Unit (PVU) License + SW Subscription & Support 12 Months
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Компьютерная библиотека: книги, статьи, полезные ссылки
Corel DRAW - от идеи до реализации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100